mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 04:36:43 +00:00
Compare commits
116 Commits
v3.14.0
...
5e136df557
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e136df557 | |||
| 9c5b5c2ac3 | |||
| 0e6df4ce73 | |||
| a4772ee4e0 | |||
| ef779a23c1 | |||
| 4f4a2c3fd8 | |||
| 120a7b3626 | |||
| 7cf3b6fb7b | |||
| dd2448f35a | |||
| 917cf3fd51 | |||
| 98c3a01e08 | |||
| ecb74dbb29 | |||
| fa9836e9cf | |||
| 29a10bc4df | |||
| 6ad1e89919 | |||
| 55a0f27ca9 | |||
| c8291c2d35 | |||
| 4d8d00a62f | |||
| f0c76b1114 | |||
| cd69a712e1 | |||
| 21029825c9 | |||
| 3f78f4d672 | |||
| 5fbe94c559 | |||
| 80d556343e | |||
| 612d1054db | |||
| acf2fc32c4 | |||
| af01c63298 | |||
| 2e98d64f94 | |||
| cdcdd45bcf | |||
| 61ca604ace | |||
| b3e2a91f56 | |||
| 7d9753e2da | |||
| f1aef383b7 | |||
| 6647231278 | |||
| 531368da86 | |||
| a8caec560e | |||
| 0c21925939 | |||
| 19a445e73a | |||
| 96e0070ed2 | |||
| 516ff5206d | |||
| 4d2b328589 | |||
| 810be2d423 | |||
| e3d0334b6f | |||
| fb523e5573 | |||
| cb8d1a2389 | |||
| 84f0a6722a | |||
| c3a495facd | |||
| 9cdc40ca05 | |||
| 7021b1c2ea | |||
| 607d9df8a9 | |||
| 93396145dc | |||
| 7457fb06d2 | |||
| bee2642aec | |||
| b481ce2203 | |||
| 040d887aae | |||
| 3710dff0cd | |||
| df78e29234 | |||
| da76784290 | |||
| 43cbedafb8 | |||
| 0d33cc5000 | |||
| ed5adb18fb | |||
| 85a91c5572 | |||
| 56d4bfbb71 | |||
| 48a75b0dd7 | |||
| b84663dd7a | |||
| cd8db6fd1c | |||
| a5e0f85a58 | |||
| 6cbe51138a | |||
| 82607efe1c | |||
| 961dc9435f | |||
| b574ccb6ea | |||
| 2569e83e51 | |||
| f34a7ff0ed | |||
| da069a0155 | |||
| 384fa4eb4b | |||
| 0c6e4ffa35 | |||
| 4951244400 | |||
| d65d6ee2e5 | |||
| 097d6f86d3 | |||
| 9894cf9744 | |||
| f84067de3e | |||
| f885bfbcf4 | |||
| f3aac09ecb | |||
| 38d692ebfb | |||
| 1acc7eb7db | |||
| 248fbf5e33 | |||
| 8b12a454ea | |||
| 310fcffc7b | |||
| 318ad16378 | |||
| 8be4246f7e | |||
| e580f89106 | |||
| 01043e033e | |||
| 94b44b383a | |||
| a3b8fabb26 | |||
| 275b30e518 | |||
| bf244e5c86 | |||
| cf9651bb94 | |||
| ba65ffdbc7 | |||
| 4b95ef4d82 | |||
| 951c7c27fb | |||
| e7423a9519 | |||
| d3582fa981 | |||
| 80c852a5b2 | |||
| 51498e3e37 | |||
| b7ef6e1486 | |||
| 0d03f84711 | |||
| 949666724d | |||
| bbe19bf960 | |||
| bfe25e3a46 | |||
| 236c958703 | |||
| e6b312b437 | |||
| 384154c767 | |||
| 45d2e9ea63 | |||
| 86e8a566c7 | |||
| a80fd92018 | |||
| 71063ac5ee |
30
.gitea/workflows/ci.yml
Normal file
30
.gitea/workflows/ci.yml
Normal file
@ -0,0 +1,30 @@
|
||||
name: nogui build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
try:
|
||||
name: try & test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.23.3'
|
||||
|
||||
- name: Install build-essentials
|
||||
run: apt update -y && apt install build-essential libsecret-1-dev -y
|
||||
|
||||
- name: build nogui
|
||||
run: make build-nogui
|
||||
|
||||
- uses: christopherhx/gitea-upload-artifact@v4
|
||||
with:
|
||||
name: nogui.zip
|
||||
path: |
|
||||
proton-bridge
|
||||
bridge
|
||||
41
.github/ISSUE_TEMPLATE/general-issue-template.md
vendored
41
.github/ISSUE_TEMPLATE/general-issue-template.md
vendored
@ -1,41 +0,0 @@
|
||||
---
|
||||
name: General issue template
|
||||
about: Template for detailed report of issues
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Issue tracker is ONLY used for reporting bugs with technical details. "It doesn't work" or new features should be discussed with our customer support. Please use bug report function in Bridge or contact bridge@protonmail.ch.
|
||||
<!--- Provide a general summary of the issue in the Title above -->
|
||||
|
||||
## Expected Behavior
|
||||
<!--- Tell us what should happen -->
|
||||
|
||||
## Current Behavior
|
||||
<!--- Tell us what happens instead of the expected behavior -->
|
||||
|
||||
## Possible Solution
|
||||
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
|
||||
|
||||
## Steps to Reproduce
|
||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
4.
|
||||
|
||||
## Version Information
|
||||
<!--- Which version of the app(s) were you using when you experienced this issue? -->
|
||||
|
||||
## Context (Environment)
|
||||
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
||||
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
|
||||
|
||||
## Detailed Description
|
||||
<!--- Provide a detailed description of the change or addition you are proposing -->
|
||||
|
||||
## Possible Implementation
|
||||
<!--- Not obligatory, but suggest an idea for implementing addition or change -->
|
||||
1
.gitlab/CODEOWNERS
Normal file
1
.gitlab/CODEOWNERS
Normal file
@ -0,0 +1 @@
|
||||
* @go/bridge-ppl/devs
|
||||
@ -88,6 +88,7 @@ linters:
|
||||
- durationcheck # check for two durations multiplied together [fast: false, auto-fix: false]
|
||||
- exhaustive # check exhaustiveness of enum switch statements [fast: false, auto-fix: false]
|
||||
- exportloopref # checks for pointers to enclosing loop variables [fast: false, auto-fix: false]
|
||||
- copyloopvar # detects places where loop variables are copied.
|
||||
- forcetypeassert # finds forced type assertions [fast: true, auto-fix: false]
|
||||
- godot # Check if comments end in a period [fast: true, auto-fix: true]
|
||||
- goheader # Checks is file header matches to pattern [fast: true, auto-fix: false]
|
||||
|
||||
@ -1,10 +1,78 @@
|
||||
# Contribution Policy
|
||||
# Contributing guidelines
|
||||
|
||||
The following document describes how to contribute to the project. In this context, contribution does not only mean code contribution but also reporting issues, requesting new features, or just asking for help.
|
||||
|
||||
## Reporting issues
|
||||
|
||||
In case you experience issues while using the application, our request is to contact Proton customer support directly.
|
||||
|
||||
The benefits of using Proton customer support are
|
||||
|
||||
- Available 24/7/365.
|
||||
- Provides priority support based on subscription type.
|
||||
- Will escalate the issue to the developers every time it becomes too technical or they do not know the answer to a question.
|
||||
- Easier to detect systematic issues by connecting similar reports.
|
||||
- Possible to quickly derive frequency of an issue.
|
||||
- Can assist you to transfer sensitive information safely to us.
|
||||
|
||||
To speed up the communication with customer support, consider the following:
|
||||
|
||||
- Whenever is possible, use the in-app bug report feature. It provides an application specific guide compared to using the generic report form on web.
|
||||
- Whenever is possible, proactively attach logs to your report. Reporting an issue from the application can help you in that.
|
||||
- Check whether your system is officially supported by Proton, including the source of the installer. We cannot provide help when the application is packaged by a third party or when the application is used on systems that we do not prepare to support.
|
||||
- If your report is a feature request, see the Feature request section. In case it is an issue related to application security, see the Security vulnerabilities section.
|
||||
|
||||
In the past, we used GitHub issue tracker for more technical issues in parallel to Proton customer support, but we run into limitations with this approach:
|
||||
|
||||
- Monitoring GitHub issue tracker took development time as it was managed by the development team.
|
||||
- It made issue frequency tracking challenging because we did not have a single point of entry for issues.
|
||||
- Users were confused what technical issue means, and used the GitHub issue tracker for feature requests, or non-technical discussions.
|
||||
- Users sometimes shared sensitive data through the GitHub issue tracker.
|
||||
|
||||
For the above reasons, we do not use GitHub issue tracker anymore but ask you to contact our customer support in case you run into a problem.
|
||||
|
||||
### Security vulnerabilities
|
||||
|
||||
Proton runs a bug bounty program for security vulnerabilities. They differ from normal bug reports in the following ways:
|
||||
|
||||
- These reports go directly to our security team.
|
||||
- They expect deeper explanation of the issue.
|
||||
- Depending on the finding, they may be financially rewarded.
|
||||
|
||||
More information about the program can be found [here](https://proton.me/security/bug-bounty).
|
||||
|
||||
## Feature requests
|
||||
|
||||
What someone considers as a bug is sometimes a feature, and sometimes, a missing feature is considered as a bug. Instead of reporting feature requests as bugs, we setup a UserVoice page to allow our users to share their preferences. UserVoice also makes it possible to vote on other feature requests, making the community preference public.
|
||||
|
||||
Our product team frequently monitors UserVoice, and the features listed there are taken into account in our planning.
|
||||
|
||||
Examples for UserVoice requests:
|
||||
|
||||
- Extending the officially supported environments (e.g., operating systems, clients, or computer architectures).
|
||||
- Requesting new features.
|
||||
- Integration with non-Proton services.
|
||||
|
||||
UserVoice is available [here](https://protonmail.uservoice.com/).
|
||||
|
||||
## Asking for help
|
||||
|
||||
The best ways to get answer for generic questions or to get help with setting up the system is to interact with our active community on [Reddit](https://reddit.com/r/ProtonMail/) or to contact customer support.
|
||||
|
||||
## Code contribution
|
||||
|
||||
We are grateful if you can contribute directly with code. In that case there is nothing else to do than to open a pull request.
|
||||
|
||||
The following is worthwhile noting
|
||||
|
||||
- The project is primarily developed on an internal repository, and the one on GitHub is only a mirror of it. For that reason, the merge request will not be merged on GitHub but added to the project internally. We are keeping the original author in the change set to respect the contribution.
|
||||
- The application is used on numerous platforms and by many third party clients. To have higher chance your change to be accepted, consider all supported dependencies.
|
||||
- Give detailed description of the issue, preferably with test steps to reproduce the original issue, and to verify the fix. It is even better if you also extend the automated tests.
|
||||
|
||||
### Contribution policy
|
||||
|
||||
By making a contribution to this project:
|
||||
|
||||
1. I assign any and all copyright related to the contribution to Proton AG;
|
||||
2. I certify that the contribution was created in whole by me;
|
||||
3. I understand and agree that this project and the contribution are public
|
||||
and that a record of the contribution (including all personal information I
|
||||
submit with it) is maintained indefinitely and may be redistributed with
|
||||
this project or the open source license(s) involved.
|
||||
1. You assign any and all copyright related to the contribution to Proton AG;
|
||||
2. You certify that the contribution was created in whole by you;
|
||||
3. You understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information you submit with it) is maintained indefinitely and may be redistributed with this project or the open source license(s) involved.
|
||||
|
||||
@ -63,11 +63,15 @@ Proton Mail Bridge includes the following 3rd party software:
|
||||
* [goleak](https://go.uber.org/goleak) available under [license](https://pkg.go.dev/go.uber.org/goleak?tab=licenses)
|
||||
* [exp](https://golang.org/x/exp) available under [license](https://cs.opensource.google/go/x/exp/+/master:LICENSE)
|
||||
* [net](https://golang.org/x/net) available under [license](https://cs.opensource.google/go/x/net/+/master:LICENSE)
|
||||
* [oauth2](https://golang.org/x/oauth2) available under [license](https://cs.opensource.google/go/x/oauth2/+/master:LICENSE)
|
||||
* [sys](https://golang.org/x/sys) available under [license](https://cs.opensource.google/go/x/sys/+/master:LICENSE)
|
||||
* [text](https://golang.org/x/text) available under [license](https://cs.opensource.google/go/x/text/+/master:LICENSE)
|
||||
* [api](https://google.golang.org/api) available under [license](https://pkg.go.dev/google.golang.org/api?tab=licenses)
|
||||
* [grpc](https://google.golang.org/grpc) available under [license](https://github.com/grpc/grpc-go/blob/master/LICENSE)
|
||||
* [protobuf](https://google.golang.org/protobuf) available under [license](https://github.com/protocolbuffers/protobuf/blob/main/LICENSE)
|
||||
* [plist](https://howett.net/plist) available under [license](https://github.com/DHowett/go-plist/blob/main/LICENSE)
|
||||
* [compute](https://cloud.google.com/go/compute) available under [license](https://pkg.go.dev/cloud.google.com/go/compute?tab=licenses)
|
||||
* [metadata](https://cloud.google.com/go/compute/metadata) available under [license](https://pkg.go.dev/cloud.google.com/go/compute/metadata?tab=licenses)
|
||||
* [bcrypt](https://github.com/ProtonMail/bcrypt) available under [license](https://github.com/ProtonMail/bcrypt/blob/master/LICENSE)
|
||||
* [go-crypto](https://github.com/ProtonMail/go-crypto) available under [license](https://github.com/ProtonMail/go-crypto/blob/master/LICENSE)
|
||||
* [go-mime](https://github.com/ProtonMail/go-mime) available under [license](https://github.com/ProtonMail/go-mime/blob/master/LICENSE)
|
||||
@ -95,8 +99,11 @@ Proton Mail Bridge includes the following 3rd party software:
|
||||
* [validator](https://github.com/go-playground/validator/v10) available under [license](https://github.com/go-playground/validator/v10/blob/master/LICENSE)
|
||||
* [go-json](https://github.com/goccy/go-json) available under [license](https://github.com/goccy/go-json/blob/master/LICENSE)
|
||||
* [uuid](https://github.com/gofrs/uuid) available under [license](https://github.com/gofrs/uuid/blob/master/LICENSE)
|
||||
* [groupcache](https://github.com/golang/groupcache) available under [license](https://github.com/golang/groupcache/blob/master/LICENSE)
|
||||
* [protobuf](https://github.com/golang/protobuf) available under [license](https://github.com/golang/protobuf/blob/master/LICENSE)
|
||||
* [pprof](https://github.com/google/pprof) available under [license](https://github.com/google/pprof/blob/master/LICENSE)
|
||||
* [enterprise-certificate-proxy](https://github.com/googleapis/enterprise-certificate-proxy) available under [license](https://github.com/googleapis/enterprise-certificate-proxy/blob/master/LICENSE)
|
||||
* [gax-go](https://github.com/googleapis/gax-go/v2) available under [license](https://github.com/googleapis/gax-go/v2/blob/master/LICENSE)
|
||||
* [errwrap](https://github.com/hashicorp/errwrap) available under [license](https://github.com/hashicorp/errwrap/blob/master/LICENSE)
|
||||
* [go-immutable-radix](https://github.com/hashicorp/go-immutable-radix) available under [license](https://github.com/hashicorp/go-immutable-radix/blob/master/LICENSE)
|
||||
* [go-memdb](https://github.com/hashicorp/go-memdb) available under [license](https://github.com/hashicorp/go-memdb/blob/master/LICENSE)
|
||||
@ -124,14 +131,16 @@ Proton Mail Bridge includes the following 3rd party software:
|
||||
* [codec](https://github.com/ugorji/go/codec) available under [license](https://github.com/ugorji/go/codec/blob/master/LICENSE)
|
||||
* [tagparser](https://github.com/vmihailenco/tagparser/v2) available under [license](https://github.com/vmihailenco/tagparser/v2/blob/master/LICENSE)
|
||||
* [smetrics](https://github.com/xrash/smetrics) available under [license](https://github.com/xrash/smetrics/blob/master/LICENSE)
|
||||
* [go-ordered-json](https://gitlab.com/c0b/go-ordered-json)
|
||||
* [go-ordered-json](https://gitlab.com/c0b/go-ordered-json) available under [license](https://gitlab.com/c0b/go-ordered-json/blob/master/LICENSE)
|
||||
* [go.opencensus.io](https://pkg.go.dev/go.opencensus.io?tab=licenses) available under [license](https://pkg.go.dev/go.opencensus.io?tab=licenses)
|
||||
* [arch](https://golang.org/x/arch) available under [license](https://cs.opensource.google/go/x/arch/+/master:LICENSE)
|
||||
* [crypto](https://golang.org/x/crypto) available under [license](https://cs.opensource.google/go/x/crypto/+/master:LICENSE)
|
||||
* [mod](https://golang.org/x/mod) available under [license](https://cs.opensource.google/go/x/mod/+/master:LICENSE)
|
||||
* [sync](https://golang.org/x/sync) available under [license](https://cs.opensource.google/go/x/sync/+/master:LICENSE)
|
||||
* [tools](https://golang.org/x/tools) available under [license](https://cs.opensource.google/go/x/tools/+/master:LICENSE)
|
||||
* [appengine](https://google.golang.org/appengine) available under [license](https://pkg.go.dev/google.golang.org/appengine?tab=licenses)
|
||||
* [genproto](https://google.golang.org/genproto) available under [license](https://pkg.go.dev/google.golang.org/genproto?tab=licenses)
|
||||
* [yaml](https://gopkg.in/yaml.v3) available under [license](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)
|
||||
* [yaml](https://gopkg.in/yaml.v3) available under [license](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE) available under [license](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)
|
||||
* [go-message](https://github.com/ProtonMail/go-message) available under [license](https://github.com/ProtonMail/go-message/blob/master/LICENSE)
|
||||
* [go-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)
|
||||
|
||||
30
Changelog.md
30
Changelog.md
@ -3,6 +3,36 @@
|
||||
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
||||
|
||||
|
||||
## Erasmus Bridge 3.15.1
|
||||
|
||||
### Changed
|
||||
* BRIDGE-281: Disable keychain test on macOS.
|
||||
|
||||
|
||||
## Erasmus Bridge 3.15.0
|
||||
|
||||
### Added
|
||||
* BRIDGE-238: Added host information to sentry events; new sentry event for keychain issues.
|
||||
* BRIDGE-236: Added SMTP observability metrics.
|
||||
* BRIDGE-217: Added missing parameter to the CLI help command.
|
||||
* BRIDGE-234: Add accessibility name in QML for UI automation.
|
||||
* BRIDGE-232: Test: Add Home Menu Bridge UI e2e automation tests.
|
||||
* BRIDGE-220: Test: Add Bridge E2E UI login/logout tests for Windows.
|
||||
|
||||
### Changed
|
||||
* BRIDGE-228: Removed sentry events.
|
||||
* BRIDGE-218: Observability adapter; gluon observability metrics and tests.
|
||||
* BRIDGE-215: Tweak wording on macOS profile install page.
|
||||
* BRIDGE-131: Test: Integration tests for messages from Proton <-> Gmail.
|
||||
* BRIDGE-142: Bridge icon can be removed from the menu bar on macOS.
|
||||
|
||||
### Fixed
|
||||
* BRIDGE-240: Fix for running against Qt 6.8 (contribution of GitHub user Cimbali).
|
||||
* BRIDGE-231: Fix reversed header order in messages.
|
||||
* BRIDGE-235: Fix compilation of Bridge GUI Tester on Windows.
|
||||
* BRIDGE-120: Use appropriate address key when importing / saving draft.
|
||||
|
||||
|
||||
## Dragon Bridge 3.14.0
|
||||
|
||||
### Changed
|
||||
|
||||
4
Makefile
4
Makefile
@ -12,7 +12,7 @@ ROOT_DIR:=$(realpath .)
|
||||
.PHONY: build build-gui build-nogui build-launcher versioner hasher
|
||||
|
||||
# Keep version hardcoded so app build works also without Git repository.
|
||||
BRIDGE_APP_VERSION?=3.14.0+git
|
||||
BRIDGE_APP_VERSION?=3.15.1+git
|
||||
APP_VERSION:=${BRIDGE_APP_VERSION}
|
||||
APP_FULL_NAME:=Proton Mail Bridge
|
||||
APP_VENDOR:=Proton AG
|
||||
@ -189,7 +189,7 @@ ${RESOURCE_FILE}: ./dist/info.rc ./dist/${SRC_ICO} .FORCE
|
||||
|
||||
## Dev dependencies
|
||||
.PHONY: install-devel-tools install-linter install-go-mod-outdated install-git-hooks
|
||||
LINTVER:="v1.59.1"
|
||||
LINTVER:="v1.61.0"
|
||||
LINTSRC:="https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh"
|
||||
|
||||
install-dev-dependencies: install-devel-tools install-linter install-go-mod-outdated
|
||||
|
||||
14
README.md
14
README.md
@ -1,7 +1,7 @@
|
||||
# Proton Mail Bridge and Import Export app
|
||||
# Proton Mail Bridge
|
||||
Copyright (c) 2024 Proton AG
|
||||
|
||||
This repository holds the Proton Mail Bridge and the Proton Mail Import-Export applications.
|
||||
This repository holds the Proton Mail Bridge application.
|
||||
For a detailed build information see [BUILDS](./BUILDS.md).
|
||||
The license can be found in [LICENSE](./LICENSE) file, for more licensing information see [COPYING_NOTES](./COPYING_NOTES.md).
|
||||
For contribution policy see [CONTRIBUTING](./CONTRIBUTING.md).
|
||||
@ -13,7 +13,7 @@ Proton Mail Bridge for e-mail clients.
|
||||
When launched, Bridge will initialize local IMAP/SMTP servers and render
|
||||
its GUI.
|
||||
|
||||
To configure an e-mail client, firstly log in using your Proton Mail credentials.
|
||||
To configure an e-mail client, first log in using your Proton Mail credentials.
|
||||
Open your e-mail client and add a new account using the settings which are
|
||||
located in the Bridge GUI. The client will only be able to sync with
|
||||
your Proton Mail account when the Bridge is running, thus the option
|
||||
@ -24,10 +24,10 @@ background.
|
||||
|
||||
More details [on the public website](https://proton.me/mail/bridge).
|
||||
|
||||
## Launchers
|
||||
Launchers are binaries used to run the Proton Mail Bridge or Import-Export apps.
|
||||
## Launcher
|
||||
The launcher is a binary used to run the Proton Mail Bridge.
|
||||
|
||||
Official distributions of the Proton Mail Bridge and Import-Export apps contain
|
||||
The Official distribution of the Proton Mail Bridge application contains
|
||||
both a launcher and the app itself. The launcher is installed in a protected
|
||||
area of the system (i.e. an area accessible only with admin privileges) and is
|
||||
used to run the app. The launcher ensures that nobody tampered with the app's
|
||||
@ -37,7 +37,7 @@ feature enables the app to securely update itself automatically without asking
|
||||
the user for a password.
|
||||
|
||||
## Keychain
|
||||
You need to have a keychain in order to run the Proton Mail Bridge. On Mac or
|
||||
You need to have a keychain in order to run Proton Mail Bridge. On Mac or
|
||||
Windows, Bridge uses native credential managers. On Linux, use `secret-service` freedesktop.org API
|
||||
(e.g. [Gnome keyring](https://wiki.gnome.org/Projects/GnomeKeyring/))
|
||||
or
|
||||
|
||||
11
go.mod
11
go.mod
@ -7,7 +7,7 @@ toolchain go1.21.9
|
||||
require (
|
||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
||||
github.com/Masterminds/semver/v3 v3.2.0
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20240923151549-d23b4bec3602
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20241121121545-aa1cfd19b4b2
|
||||
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/gopenpgp/v2 v2.7.4-proton
|
||||
@ -47,14 +47,18 @@ require (
|
||||
go.uber.org/goleak v1.2.1
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/oauth2 v0.7.0
|
||||
golang.org/x/sys v0.19.0
|
||||
golang.org/x/text v0.14.0
|
||||
google.golang.org/api v0.114.0
|
||||
google.golang.org/grpc v1.56.3
|
||||
google.golang.org/protobuf v1.33.0
|
||||
howett.net/plist v1.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.19.1 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233 // indirect
|
||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
|
||||
@ -82,8 +86,11 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gofrs/uuid v4.3.0+incompatible // indirect
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.7.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-memdb v1.3.3 // indirect
|
||||
@ -112,11 +119,13 @@ require (
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
97
go.sum
97
go.sum
@ -5,9 +5,16 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY=
|
||||
cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM=
|
||||
cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
@ -27,51 +34,17 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
|
||||
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
|
||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug=
|
||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20240514133734-79cdd0fec41c h1:P3SvCACt13Zqdj0IRDB4bgwqI68+oMB2j0uVuPQyoTw=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20240514133734-79cdd0fec41c/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20240918150504-3b2e7f40d961 h1:kCaz78X7OKETvK6AGHeyggHKxDBcqX7EWHf7spJ+D3g=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20240918150504-3b2e7f40d961/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20240923094038-e319bf6047c5 h1:LzaUpUj6M2PEBArFCkaimViNpGXDgwHVrdhvYwHLoJQ=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20240923094038-e319bf6047c5/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20240923151549-d23b4bec3602 h1:EoMjWlC32tg46L/07hWoiZfLkqJyxVMcsq4Cyn+Ofqc=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20240923151549-d23b4bec3602/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20241121121545-aa1cfd19b4b2 h1:iZjKvjb6VkGb52ZaBBiXC1MGYJN4C/S97JfppdzpMHQ=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20241121121545-aa1cfd19b4b2/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233 h1:bdoKdh0f66/lrgVfYlxw0aqISY/KOqXmFJyGt7rGmnc=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7 h1:+j+Kd/DyZ/qGfMT9htAT7HxqIEbZHsatsx+m8AoV6fc=
|
||||
github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
|
||||
github.com/ProtonMail/go-message v0.13.1-0.20240906141354-38c596f2f5a8 h1:+eE7FGX+4Hu8RZaRmSebrDVXyLuowKSaO7ZhQ6ca4+E=
|
||||
github.com/ProtonMail/go-message v0.13.1-0.20240906141354-38c596f2f5a8/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
|
||||
github.com/ProtonMail/go-message v0.13.1-0.20240906144417-4083506a9542 h1:5DqSycYnKfUdHiu0yOdiYW5R2hVxoE0Mk4PLSYwqGyg=
|
||||
github.com/ProtonMail/go-message v0.13.1-0.20240906144417-4083506a9542/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
|
||||
github.com/ProtonMail/go-message v0.13.1-0.20240910093530-2ada52e7dffb h1:uOKp93u6JFYlBoJJvOhzmHZURcvWmXiqhihGWtT3HtY=
|
||||
github.com/ProtonMail/go-message v0.13.1-0.20240910093530-2ada52e7dffb/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
|
||||
github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423 h1:p8nBDxvRnvDOyrcePKkPpErWGhDoTqpX8a1c54CcSu0=
|
||||
github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240612082117-0f92424eed80 h1:cP4+6RFn9vVgYnoDwxBU4EtIAZA+eM4rzOaSZNqZ1xg=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240612082117-0f92424eed80/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240808145610-88df257767f6 h1:nERxOYS4ndSgWEr834YYkb1j0bZK/dJAmhoyYB1MtNY=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240808145610-88df257767f6/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240819131705-149e50199c5b h1:zifGh4LS5HwQIaVCccSe5/oJGTOjFeVObMRl3QJoJ3k=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240819131705-149e50199c5b/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240821081056-dd607af0f917 h1:Ma6PfXFDuw7rYYq28FXNW6ubhYquRUmBuLyZrjJWHUE=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240821081056-dd607af0f917/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240822150235-7a6190889179 h1:6Xo0iRYa4GBgZ2HA+IR3KdqiML8Z10h2F9TYe+9n1+M=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240822150235-7a6190889179/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240827084449-71096377c391 h1:PW6bE+mhsfAx4+wDCCNjhFrCNiiuMjY6j7RwqRUdPKI=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240827084449-71096377c391/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240827122236-ca6bb6449bba h1:QtDxgIbgPqRQg7VT+nIUJlaOyNFAoGyg59oW3Hji/0A=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240827122236-ca6bb6449bba/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240827132526-849231fc34a1 h1:gATlMoj4raG32WyGGh8SpipoQeR2AlU7g+8NAMicTcw=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240827132526-849231fc34a1/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240829112804-d663a2ef90c2 h1:yx0iejqB5c21HIN5jn9IsbyzUns0dPUUaGfyUHF3TmQ=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240829112804-d663a2ef90c2/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240916123336-3ac75d8041dc h1:SWVPwO1M2jCI1bJHBji/JVU01FpWP/6nzh8NBIjo+Fg=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240916123336-3ac75d8041dc/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240918100656-b4860af56d47 h1:a+3dOyIxJEslN5HxyICM8flY9lnCyJupXNcv6fUaivA=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240918100656-b4860af56d47/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865 h1:EP1gnxLL5Z7xBSymE9nSTM27nRYINuvssAtDmG0suD8=
|
||||
@ -108,6 +81,7 @@ github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
@ -124,6 +98,7 @@ github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtM
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
@ -175,6 +150,10 @@ github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwo
|
||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||
github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3 h1:hQ1wTMaKcGfobYRT88RM8NFNyX+IQHvagkm/tqViU98=
|
||||
github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
@ -227,6 +206,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69
|
||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
@ -235,6 +216,13 @@ github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+Licev
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
@ -242,6 +230,10 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
@ -252,10 +244,15 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A=
|
||||
github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
@ -401,6 +398,7 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
@ -482,6 +480,8 @@ gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a/go.mod h1:NREv
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||
@ -496,6 +496,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
@ -544,6 +545,7 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
@ -561,6 +563,8 @@ golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
|
||||
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -591,6 +595,7 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -650,6 +655,7 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
@ -674,10 +680,14 @@ google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE=
|
||||
google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
@ -687,13 +697,27 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
|
||||
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
@ -721,6 +745,7 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
|
||||
@ -75,17 +75,21 @@ const (
|
||||
|
||||
flagLogIMAP = "log-imap"
|
||||
flagLogSMTP = "log-smtp"
|
||||
|
||||
flagEnableKeychainTest = "enable-keychain-test"
|
||||
flagDisableKeychainTest = "disable-keychain-test"
|
||||
|
||||
flagSoftwareRenderer = "software-renderer"
|
||||
flagSetSoftwareRenderer = "set-software-renderer"
|
||||
flagSetHardwareRenderer = "set-hardware-renderer"
|
||||
)
|
||||
|
||||
// Hidden flags.
|
||||
const (
|
||||
flagLauncher = "launcher"
|
||||
flagNoWindow = "no-window"
|
||||
flagParentPID = "parent-pid"
|
||||
flagSoftwareRenderer = "software-renderer"
|
||||
flagEnableKeychainTest = "enable-keychain-test"
|
||||
flagDisableKeychainTest = "disable-keychain-test"
|
||||
FlagSessionID = "session-id"
|
||||
flagLauncher = "launcher"
|
||||
flagNoWindow = "no-window"
|
||||
flagParentPID = "parent-pid"
|
||||
FlagSessionID = "session-id"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -93,18 +97,21 @@ const (
|
||||
appShortName = "bridge"
|
||||
)
|
||||
|
||||
// the two flags below have been deprecated by BRIDGE-281. We however keep them so that bridge does not error if they are passed on startup.
|
||||
var cliFlagEnableKeychainTest = &cli.BoolFlag{ //nolint:gochecknoglobals
|
||||
Name: flagEnableKeychainTest,
|
||||
Usage: "Enable the keychain test",
|
||||
Hidden: true,
|
||||
Value: false,
|
||||
} //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: "Disable the keychain test",
|
||||
Hidden: true,
|
||||
Value: false,
|
||||
Name: flagDisableKeychainTest,
|
||||
Usage: "This flag is deprecated and does nothing",
|
||||
Value: false,
|
||||
DisableDefaultText: true,
|
||||
Hidden: true,
|
||||
}
|
||||
|
||||
func New() *cli.App {
|
||||
@ -156,6 +163,24 @@ func New() *cli.App {
|
||||
Name: flagLogSMTP,
|
||||
Usage: "Enable logging of SMTP communications (may contain decrypted data!)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: flagSoftwareRenderer, // This flag is ignored by bridge, but should be passed to launcher in case of restart, so it need to be accepted by the CLI parser.
|
||||
Usage: "Use software rendering of the GUI for the current execution of the application",
|
||||
Value: false,
|
||||
DisableDefaultText: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: flagSetSoftwareRenderer, // This flag is ignored by bridge, we just want it to be shown in the help (BRIDGE-217).
|
||||
Usage: "Toggle software rendering of the GUI for the current and future executions of the application",
|
||||
Value: false,
|
||||
DisableDefaultText: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: flagSetHardwareRenderer, // This flag is ignored by bridge, we just want it to be shown in the help (BRIDGE-217).
|
||||
Usage: "Toggle hardware rendering of the GUI for the current and future executions of the application",
|
||||
Value: false,
|
||||
DisableDefaultText: true,
|
||||
},
|
||||
|
||||
// Hidden flags
|
||||
&cli.BoolFlag{
|
||||
@ -174,19 +199,24 @@ func New() *cli.App {
|
||||
Hidden: true,
|
||||
Value: -1,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: flagSoftwareRenderer, // This flag is ignored by bridge, but should be passed to launcher in case of restart, so it need to be accepted by the CLI parser.
|
||||
Usage: "GUI is using software renderer",
|
||||
Hidden: true,
|
||||
Value: false,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: FlagSessionID,
|
||||
Hidden: true,
|
||||
},
|
||||
// the two flags below were introduced by BRIDGE-116
|
||||
cliFlagEnableKeychainTest,
|
||||
cliFlagDisableKeychainTest,
|
||||
}
|
||||
|
||||
// We override the default help value because we want "Show" to be capitalized
|
||||
cli.HelpFlag = &cli.BoolFlag{
|
||||
Name: "help",
|
||||
Aliases: []string{"h"},
|
||||
Usage: "Show help",
|
||||
DisableDefaultText: true,
|
||||
}
|
||||
|
||||
if onMacOS() {
|
||||
// The two flags below were introduced for BRIDGE-116, and are available only on macOS.
|
||||
// They have been later removed fro BRIDGE-281.
|
||||
app.Flags = append(app.Flags, cliFlagEnableKeychainTest, cliFlagDisableKeychainTest)
|
||||
}
|
||||
|
||||
app.Action = run
|
||||
@ -257,10 +287,9 @@ func run(c *cli.Context) error {
|
||||
|
||||
return withSingleInstance(settings, locations.GetLockFile(), version, func() error {
|
||||
// Look for available keychains
|
||||
skipKeychainTest := checkSkipKeychainTest(c, settings)
|
||||
return WithKeychainList(crashHandler, skipKeychainTest, func(keychains *keychain.List) error {
|
||||
return WithKeychainList(crashHandler, func(keychains *keychain.List) error {
|
||||
// Unlock the encrypted vault.
|
||||
return WithVault(locations, keychains, crashHandler, func(v *vault.Vault, insecure, corrupt bool) error {
|
||||
return WithVault(reporter, locations, keychains, crashHandler, func(v *vault.Vault, insecure, corrupt bool) error {
|
||||
if !v.Migrated() {
|
||||
// Migrate old settings into the vault.
|
||||
if err := migrateOldSettings(v); err != nil {
|
||||
@ -522,11 +551,11 @@ func withCookieJar(vault *vault.Vault, fn func(http.CookieJar) error) error {
|
||||
}
|
||||
|
||||
// WithKeychainList init the list of usable keychains.
|
||||
func WithKeychainList(panicHandler async.PanicHandler, skipKeychainTest bool, fn func(*keychain.List) error) error {
|
||||
func WithKeychainList(panicHandler async.PanicHandler, fn func(*keychain.List) error) error {
|
||||
logrus.Debug("Creating keychain list")
|
||||
defer logrus.Debug("Keychain list stop")
|
||||
defer async.HandlePanic(panicHandler)
|
||||
return fn(keychain.NewList(skipKeychainTest))
|
||||
return fn(keychain.NewList())
|
||||
}
|
||||
|
||||
func setDeviceCookies(jar *cookies.Jar) error {
|
||||
@ -547,34 +576,6 @@ func setDeviceCookies(jar *cookies.Jar) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkSkipKeychainTest(c *cli.Context, settingsDir string) bool {
|
||||
if runtime.GOOS != "darwin" {
|
||||
return false
|
||||
}
|
||||
|
||||
enable := c.Bool(flagEnableKeychainTest)
|
||||
disable := c.Bool(flagDisableKeychainTest)
|
||||
|
||||
skip, err := vault.GetShouldSkipKeychainTest(settingsDir)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("Could not load keychain settings.")
|
||||
}
|
||||
|
||||
if (!enable) && (!disable) {
|
||||
return skip
|
||||
}
|
||||
|
||||
// if both switches are passed, 'enable' has priority
|
||||
if disable {
|
||||
skip = true
|
||||
}
|
||||
if enable {
|
||||
skip = false
|
||||
}
|
||||
|
||||
if err := vault.SetShouldSkipKeychainTest(settingsDir, skip); err != nil {
|
||||
logrus.WithError(err).Error("Could not save keychain settings.")
|
||||
}
|
||||
|
||||
return skip
|
||||
func onMacOS() bool {
|
||||
return runtime.GOOS == "darwin"
|
||||
}
|
||||
|
||||
@ -1,65 +0,0 @@
|
||||
// Copyright (c) 2024 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func TestCheckSkipKeychainTest(t *testing.T) {
|
||||
var expectedResult bool
|
||||
dir := t.TempDir()
|
||||
app := cli.App{
|
||||
Flags: []cli.Flag{
|
||||
cliFlagEnableKeychainTest,
|
||||
cliFlagDisableKeychainTest,
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
require.Equal(t, expectedResult, checkSkipKeychainTest(c, dir))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
noArgs := []string{"appName"}
|
||||
enableArgs := []string{"appName", "-" + flagEnableKeychainTest}
|
||||
disableArgs := []string{"appName", "-" + flagDisableKeychainTest}
|
||||
bothArgs := []string{"appName", "-" + flagDisableKeychainTest, "-" + flagEnableKeychainTest}
|
||||
|
||||
const trueOnlyOnMac = runtime.GOOS == "darwin"
|
||||
|
||||
expectedResult = false
|
||||
require.NoError(t, app.Run(noArgs))
|
||||
|
||||
expectedResult = trueOnlyOnMac
|
||||
require.NoError(t, app.Run(disableArgs))
|
||||
require.NoError(t, app.Run(noArgs))
|
||||
|
||||
expectedResult = false
|
||||
require.NoError(t, app.Run(enableArgs))
|
||||
require.NoError(t, app.Run(noArgs))
|
||||
|
||||
expectedResult = trueOnlyOnMac
|
||||
require.NoError(t, app.Run(disableArgs))
|
||||
|
||||
expectedResult = false
|
||||
require.NoError(t, app.Run(bothArgs))
|
||||
}
|
||||
@ -25,17 +25,18 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func WithVault(locations *locations.Locations, keychains *keychain.List, panicHandler async.PanicHandler, fn func(*vault.Vault, bool, bool) error) error {
|
||||
func WithVault(reporter *sentry.Reporter, locations *locations.Locations, keychains *keychain.List, panicHandler async.PanicHandler, fn func(*vault.Vault, bool, bool) error) error {
|
||||
logrus.Debug("Creating vault")
|
||||
defer logrus.Debug("Vault stopped")
|
||||
|
||||
// Create the encVault.
|
||||
encVault, insecure, corrupt, err := newVault(locations, keychains, panicHandler)
|
||||
encVault, insecure, corrupt, err := newVault(reporter, locations, keychains, panicHandler)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create vault: %w", err)
|
||||
}
|
||||
@ -57,7 +58,7 @@ func WithVault(locations *locations.Locations, keychains *keychain.List, panicHa
|
||||
return fn(encVault, insecure, corrupt != nil)
|
||||
}
|
||||
|
||||
func newVault(locations *locations.Locations, keychains *keychain.List, panicHandler async.PanicHandler) (*vault.Vault, bool, error, error) {
|
||||
func newVault(reporter *sentry.Reporter, locations *locations.Locations, keychains *keychain.List, panicHandler async.PanicHandler) (*vault.Vault, bool, error, error) {
|
||||
vaultDir, err := locations.ProvideSettingsPath()
|
||||
if err != nil {
|
||||
return nil, false, nil, fmt.Errorf("could not get vault dir: %w", err)
|
||||
@ -71,6 +72,16 @@ func newVault(locations *locations.Locations, keychains *keychain.List, panicHan
|
||||
)
|
||||
|
||||
if key, err := loadVaultKey(vaultDir, keychains); err != nil {
|
||||
if reporter != nil {
|
||||
if rerr := reporter.ReportMessageWithContext("Could not load/create vault key", map[string]any{
|
||||
"keychainDefaultHelper": keychains.GetDefaultHelper(),
|
||||
"keychainUsableHelpersLength": len(keychains.GetHelpers()),
|
||||
"error": err.Error(),
|
||||
}); rerr != nil {
|
||||
logrus.WithError(err).Info("Failed to report keychain issue to Sentry")
|
||||
}
|
||||
}
|
||||
|
||||
logrus.WithError(err).Error("Could not load/create vault key")
|
||||
insecure = true
|
||||
|
||||
|
||||
@ -325,6 +325,7 @@ func newBridge(
|
||||
reporter,
|
||||
uidValidityGenerator,
|
||||
&bridgeIMAPSMTPTelemetry{b: bridge},
|
||||
observabilityService,
|
||||
)
|
||||
|
||||
// Check whether username has changed and correct (macOS only)
|
||||
@ -635,14 +636,6 @@ func loadTLSConfig(vault *vault.Vault) (*tls.Config, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func min(a, b time.Duration) time.Duration {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (bridge *Bridge) HasAPIConnection() bool {
|
||||
return bridge.api.GetStatus() == proton.StatusUp
|
||||
}
|
||||
@ -722,3 +715,18 @@ func (bridge *Bridge) PushDistinctObservabilityMetrics(errType observability.Dis
|
||||
func (bridge *Bridge) ModifyObservabilityHeartbeatInterval(duration time.Duration) {
|
||||
bridge.observabilityService.ModifyHeartbeatInterval(duration)
|
||||
}
|
||||
|
||||
func (bridge *Bridge) ReportMessageWithContext(message string, messageCtx reporter.Context) {
|
||||
if err := bridge.reporter.ReportMessageWithContext(message, messageCtx); err != nil {
|
||||
logPkg.WithFields(logrus.Fields{
|
||||
"err": err,
|
||||
"sentryMessage": message,
|
||||
"messageCtx": messageCtx,
|
||||
}).Info("Error occurred when sending Report to Sentry")
|
||||
}
|
||||
}
|
||||
|
||||
// GetUsers is only used for testing purposes.
|
||||
func (bridge *Bridge) GetUsers() map[string]*user.User {
|
||||
return bridge.users
|
||||
}
|
||||
|
||||
@ -25,7 +25,6 @@ import (
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||
)
|
||||
|
||||
@ -80,12 +79,6 @@ func (bridge *Bridge) ReportBug(ctx context.Context, report *ReportBugReq) error
|
||||
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
|
||||
for i, att := range attachments {
|
||||
if i == 0 && report.IncludeLogs {
|
||||
|
||||
@ -1,46 +0,0 @@
|
||||
// Copyright (c) 2024 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||
)
|
||||
|
||||
func (bridge *Bridge) ReportBugClicked() {
|
||||
safe.RLock(func() {
|
||||
for _, user := range bridge.users {
|
||||
user.ReportBugClicked()
|
||||
}
|
||||
}, bridge.usersLock)
|
||||
}
|
||||
|
||||
func (bridge *Bridge) AutoconfigUsed(client string) {
|
||||
safe.RLock(func() {
|
||||
for _, user := range bridge.users {
|
||||
user.AutoconfigUsed(client)
|
||||
}
|
||||
}, bridge.usersLock)
|
||||
}
|
||||
|
||||
func (bridge *Bridge) ExternalLinkClicked(article string) {
|
||||
safe.RLock(func() {
|
||||
for _, user := range bridge.users {
|
||||
user.ExternalLinkClicked(article)
|
||||
}
|
||||
}, bridge.usersLock)
|
||||
}
|
||||
@ -73,15 +73,15 @@ func (h *heartBeatState) init(bridge *Bridge, manager telemetry.HeartbeatManager
|
||||
for _, user := range bridge.users {
|
||||
if user.GetAddressMode() == vault.SplitMode {
|
||||
splitMode = true
|
||||
break
|
||||
}
|
||||
h.SetUserPlan(user.GetUserPlanName())
|
||||
}
|
||||
var nbAccount = len(bridge.users)
|
||||
h.SetNbAccount(nbAccount)
|
||||
var numberConnectedAccounts = len(bridge.users)
|
||||
h.SetNumberConnectedAccounts(numberConnectedAccounts)
|
||||
h.SetSplitMode(splitMode)
|
||||
|
||||
// Do not try to send if there is no user yet.
|
||||
if nbAccount > 0 {
|
||||
if numberConnectedAccounts > 0 {
|
||||
defer h.start()
|
||||
}
|
||||
}, bridge.usersLock)
|
||||
|
||||
@ -17,7 +17,9 @@
|
||||
|
||||
package bridge
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (bridge *Bridge) GetCurrentUserAgent() string {
|
||||
return bridge.identifier.GetUserAgent()
|
||||
@ -30,6 +32,8 @@ func (bridge *Bridge) SetCurrentPlatform(platform string) {
|
||||
func (bridge *Bridge) setUserAgent(name, version string) {
|
||||
currentUserAgent := bridge.identifier.GetClientString()
|
||||
|
||||
bridge.heartbeat.SetContactedByAppleNotes(name)
|
||||
|
||||
bridge.identifier.SetClient(name, version)
|
||||
|
||||
newUserAgent := bridge.identifier.GetClientString()
|
||||
@ -54,6 +58,7 @@ func (b *bridgeUserAgentUpdater) HasClient() bool {
|
||||
}
|
||||
|
||||
func (b *bridgeUserAgentUpdater) SetClient(name, version string) {
|
||||
b.heartbeat.SetContactedByAppleNotes(name)
|
||||
b.identifier.SetClient(name, version)
|
||||
}
|
||||
|
||||
|
||||
@ -26,6 +26,7 @@ import (
|
||||
imapEvents "github.com/ProtonMail/gluon/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/unleash"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@ -93,6 +94,10 @@ func (b *bridgeIMAPSettings) LogServer() bool {
|
||||
return b.b.logIMAPServer
|
||||
}
|
||||
|
||||
func (b *bridgeIMAPSettings) DisableIMAPAuthenticate() bool {
|
||||
return b.b.unleashService.GetFlagValue(unleash.IMAPAuthenticateCommandDisabled)
|
||||
}
|
||||
|
||||
func (b *bridgeIMAPSettings) Port() int {
|
||||
return b.b.vault.GetIMAPPort()
|
||||
}
|
||||
|
||||
@ -318,11 +318,10 @@ func (bridge *Bridge) GetKnowledgeBaseSuggestions(userInput string) (kb.ArticleL
|
||||
// Note: it does not clear the keychain. The only entry in the keychain is the vault password,
|
||||
// which we need at next startup to decrypt the vault.
|
||||
func (bridge *Bridge) FactoryReset(ctx context.Context) {
|
||||
useTelemetry := !bridge.GetTelemetryDisabled()
|
||||
// Delete all the users.
|
||||
safe.Lock(func() {
|
||||
for _, user := range bridge.users {
|
||||
bridge.logoutUser(ctx, user, true, true, useTelemetry)
|
||||
bridge.logoutUser(ctx, user, true, true)
|
||||
}
|
||||
}, bridge.usersLock)
|
||||
|
||||
|
||||
@ -28,7 +28,6 @@ type Locator interface {
|
||||
ProvideLogsPath() (string, error)
|
||||
ProvideGluonCachePath() (string, error)
|
||||
ProvideGluonDataPath() (string, error)
|
||||
ProvideStatsPath() (string, error)
|
||||
GetLicenseFilePath() string
|
||||
GetDependencyLicensesLink() string
|
||||
Clear(...string) error
|
||||
|
||||
@ -33,6 +33,7 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice"
|
||||
"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/vault"
|
||||
"github.com/go-resty/resty/v2"
|
||||
@ -255,7 +256,7 @@ func (bridge *Bridge) LogoutUser(ctx context.Context, userID string) error {
|
||||
return ErrNoSuchUser
|
||||
}
|
||||
|
||||
bridge.logoutUser(ctx, user, true, false, false)
|
||||
bridge.logoutUser(ctx, user, true, false)
|
||||
|
||||
bridge.publish(events.UserLoggedOut{
|
||||
UserID: userID,
|
||||
@ -280,7 +281,7 @@ func (bridge *Bridge) DeleteUser(ctx context.Context, userID string) error {
|
||||
}
|
||||
|
||||
if user, ok := bridge.users[userID]; ok {
|
||||
bridge.logoutUser(ctx, user, true, true, !bridge.GetTelemetryDisabled())
|
||||
bridge.logoutUser(ctx, user, true, true)
|
||||
}
|
||||
|
||||
if err := imapservice.DeleteSyncState(syncConfigDir, userID); err != nil {
|
||||
@ -355,24 +356,10 @@ func (bridge *Bridge) SendBadEventUserFeedback(_ context.Context, userID string,
|
||||
}
|
||||
|
||||
if doResync {
|
||||
if rerr := bridge.reporter.ReportMessageWithContext(
|
||||
"Failed to handle event: feedback resync",
|
||||
reporter.Context{"user_id": userID},
|
||||
); rerr != nil {
|
||||
logUser.WithError(rerr).Error("Failed to report feedback failure")
|
||||
}
|
||||
|
||||
return user.BadEventFeedbackResync(ctx)
|
||||
}
|
||||
|
||||
if rerr := bridge.reporter.ReportMessageWithContext(
|
||||
"Failed to handle event: feedback logout",
|
||||
reporter.Context{"user_id": userID},
|
||||
); rerr != nil {
|
||||
logUser.WithError(rerr).Error("Failed to report feedback failure")
|
||||
}
|
||||
|
||||
bridge.logoutUser(ctx, user, true, false, false)
|
||||
bridge.logoutUser(ctx, user, true, false)
|
||||
|
||||
bridge.publish(events.UserLoggedOut{
|
||||
UserID: userID,
|
||||
@ -541,11 +528,6 @@ func (bridge *Bridge) addUserWithVault(
|
||||
vault *vault.User,
|
||||
isNew bool,
|
||||
) error {
|
||||
statsPath, err := bridge.locator.ProvideStatsPath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get Statistics directory: %w", err)
|
||||
}
|
||||
|
||||
syncSettingsPath, err := bridge.locator.ProvideIMAPSyncConfigPath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get IMAP sync config path: %w", err)
|
||||
@ -560,7 +542,6 @@ func (bridge *Bridge) addUserWithVault(
|
||||
bridge.panicHandler,
|
||||
bridge.vault.GetShowAllMail(),
|
||||
bridge.vault.GetMaxSyncMemory(),
|
||||
statsPath,
|
||||
bridge,
|
||||
bridge.serverManager,
|
||||
bridge.serverManager,
|
||||
@ -603,9 +584,12 @@ func (bridge *Bridge) addUserWithVault(
|
||||
// Finally, save the user in the bridge.
|
||||
safe.Lock(func() {
|
||||
bridge.users[apiUser.ID] = user
|
||||
bridge.heartbeat.SetNbAccount(len(bridge.users))
|
||||
bridge.heartbeat.SetNumberConnectedAccounts(len(bridge.users))
|
||||
}, 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.
|
||||
bridge.heartbeat.start()
|
||||
|
||||
@ -624,26 +608,21 @@ func (bridge *Bridge) newVaultUser(
|
||||
return bridge.vault.GetOrAddUser(apiUser.ID, apiUser.Name, apiUser.Email, authUID, authRef, saltedKeyPass)
|
||||
}
|
||||
|
||||
// logout logs out the given user, optionally logging them out from the API too.
|
||||
func (bridge *Bridge) logoutUser(ctx context.Context, user *user.User, withAPI, withData, withTelemetry bool) {
|
||||
// logoutUser logs out the given user, optionally logging them out from the API and deleting user related gluon data.
|
||||
func (bridge *Bridge) logoutUser(ctx context.Context, user *user.User, withAPI, withData bool) {
|
||||
defer delete(bridge.users, user.ID())
|
||||
|
||||
// if this is actually a remove account
|
||||
if withData && withAPI {
|
||||
user.SendConfigStatusAbort(ctx, withTelemetry)
|
||||
}
|
||||
|
||||
logUser.WithFields(logrus.Fields{
|
||||
"userID": user.ID(),
|
||||
"withAPI": withAPI,
|
||||
"withData": withData,
|
||||
}).Debug("Logging out user")
|
||||
|
||||
if err := user.Logout(ctx, withAPI); err != nil {
|
||||
if err := user.Logout(ctx, withAPI, withData, bridge.unleashService.GetFlagValue(unleash.UserRemovalGluonDataCleanupDisabled)); err != nil {
|
||||
logUser.WithError(err).Error("Failed to logout user")
|
||||
}
|
||||
|
||||
bridge.heartbeat.SetNbAccount(len(bridge.users))
|
||||
bridge.heartbeat.SetNumberConnectedAccounts(len(bridge.users) - 1)
|
||||
|
||||
user.Close()
|
||||
}
|
||||
|
||||
@ -43,8 +43,7 @@ func (bridge *Bridge) handleUserEvent(ctx context.Context, user *user.User, even
|
||||
|
||||
func (bridge *Bridge) handleUserDeauth(ctx context.Context, user *user.User) {
|
||||
safe.Lock(func() {
|
||||
bridge.logoutUser(ctx, user, false, false, false)
|
||||
user.ReportConfigStatusFailure("User deauth.")
|
||||
bridge.logoutUser(ctx, user, false, false)
|
||||
}, bridge.usersLock)
|
||||
}
|
||||
|
||||
|
||||
@ -1,228 +0,0 @@
|
||||
// Copyright (c) 2024 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package configstatus
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const version = "1.0.0"
|
||||
|
||||
func LoadConfigurationStatus(filepath string) (*ConfigurationStatus, error) {
|
||||
status := ConfigurationStatus{
|
||||
FilePath: filepath,
|
||||
DataLock: safe.NewRWMutex(),
|
||||
Data: &ConfigurationStatusData{},
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filepath); err == nil {
|
||||
if err := status.Load(); err == nil {
|
||||
return &status, nil
|
||||
}
|
||||
logrus.WithError(err).Warn("Cannot load configuration status file. Reset it.")
|
||||
}
|
||||
|
||||
status.Data.init()
|
||||
if err := status.Save(); err != nil {
|
||||
return &status, err
|
||||
}
|
||||
return &status, nil
|
||||
}
|
||||
|
||||
func (status *ConfigurationStatus) Load() error {
|
||||
bytes, err := os.ReadFile(status.FilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var metadata MetadataOnly
|
||||
if err := json.Unmarshal(bytes, &metadata); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if metadata.Metadata.Version != version {
|
||||
return fmt.Errorf("unsupported configstatus file version %s", metadata.Metadata.Version)
|
||||
}
|
||||
|
||||
return json.Unmarshal(bytes, status.Data)
|
||||
}
|
||||
|
||||
func (status *ConfigurationStatus) Save() error {
|
||||
temp := status.FilePath + "_temp"
|
||||
f, err := os.Create(temp) //nolint:gosec
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
enc := json.NewEncoder(f)
|
||||
enc.SetIndent("", " ")
|
||||
err = enc.Encode(status.Data)
|
||||
if err := f.Close(); err != nil {
|
||||
logrus.WithError(err).Error("Error while closing configstatus file.")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Rename(temp, status.FilePath)
|
||||
}
|
||||
|
||||
func (status *ConfigurationStatus) IsPending() bool {
|
||||
status.DataLock.RLock()
|
||||
defer status.DataLock.RUnlock()
|
||||
|
||||
return !status.Data.DataV1.PendingSince.IsZero()
|
||||
}
|
||||
|
||||
func (status *ConfigurationStatus) isPendingSinceMin() int {
|
||||
if min := int(time.Since(status.Data.DataV1.PendingSince).Minutes()); min > 0 {
|
||||
return min
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (status *ConfigurationStatus) IsFromFailure() bool {
|
||||
status.DataLock.RLock()
|
||||
defer status.DataLock.RUnlock()
|
||||
|
||||
return status.Data.DataV1.FailureDetails != ""
|
||||
}
|
||||
|
||||
func (status *ConfigurationStatus) ApplySuccess() error {
|
||||
status.DataLock.Lock()
|
||||
defer status.DataLock.Unlock()
|
||||
|
||||
status.Data.init()
|
||||
status.Data.DataV1.PendingSince = time.Time{}
|
||||
return status.Save()
|
||||
}
|
||||
|
||||
func (status *ConfigurationStatus) ApplyFailure(err string) error {
|
||||
status.DataLock.Lock()
|
||||
defer status.DataLock.Unlock()
|
||||
|
||||
status.Data.init()
|
||||
status.Data.DataV1.FailureDetails = err
|
||||
return status.Save()
|
||||
}
|
||||
|
||||
func (status *ConfigurationStatus) ApplyProgress() error {
|
||||
status.DataLock.Lock()
|
||||
defer status.DataLock.Unlock()
|
||||
|
||||
status.Data.DataV1.LastProgress = time.Now()
|
||||
return status.Save()
|
||||
}
|
||||
|
||||
func (status *ConfigurationStatus) RecordLinkClicked(link uint64) error {
|
||||
status.DataLock.Lock()
|
||||
defer status.DataLock.Unlock()
|
||||
|
||||
if !status.Data.hasLinkClicked(link) {
|
||||
status.Data.setClickedLink(link)
|
||||
return status.Save()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (status *ConfigurationStatus) ReportClicked() error {
|
||||
status.DataLock.Lock()
|
||||
defer status.DataLock.Unlock()
|
||||
|
||||
if !status.Data.DataV1.ReportClick {
|
||||
status.Data.DataV1.ReportClick = true
|
||||
return status.Save()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (status *ConfigurationStatus) ReportSent() error {
|
||||
status.DataLock.Lock()
|
||||
defer status.DataLock.Unlock()
|
||||
|
||||
if !status.Data.DataV1.ReportSent {
|
||||
status.Data.DataV1.ReportSent = true
|
||||
return status.Save()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (status *ConfigurationStatus) AutoconfigUsed(client string) error {
|
||||
status.DataLock.Lock()
|
||||
defer status.DataLock.Unlock()
|
||||
|
||||
if client != status.Data.DataV1.Autoconf {
|
||||
status.Data.DataV1.Autoconf = client
|
||||
return status.Save()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (status *ConfigurationStatus) Remove() error {
|
||||
status.DataLock.Lock()
|
||||
defer status.DataLock.Unlock()
|
||||
return os.Remove(status.FilePath)
|
||||
}
|
||||
|
||||
func (data *ConfigurationStatusData) init() {
|
||||
data.Metadata = Metadata{
|
||||
Version: version,
|
||||
}
|
||||
data.DataV1.PendingSince = time.Now()
|
||||
data.DataV1.LastProgress = time.Time{}
|
||||
data.DataV1.Autoconf = ""
|
||||
data.DataV1.ClickedLink = 0
|
||||
data.DataV1.ReportSent = false
|
||||
data.DataV1.ReportClick = false
|
||||
data.DataV1.FailureDetails = ""
|
||||
}
|
||||
|
||||
func (data *ConfigurationStatusData) setClickedLink(pos uint64) {
|
||||
data.DataV1.ClickedLink |= 1 << pos
|
||||
}
|
||||
|
||||
func (data *ConfigurationStatusData) hasLinkClicked(pos uint64) bool {
|
||||
val := data.DataV1.ClickedLink & (1 << pos)
|
||||
return val > 0
|
||||
}
|
||||
|
||||
func (data *ConfigurationStatusData) clickedLinkToString() string {
|
||||
var str = ""
|
||||
var first = true
|
||||
for i := 0; i < 64; i++ {
|
||||
if data.hasLinkClicked(uint64(i)) {
|
||||
if !first {
|
||||
str += ","
|
||||
} else {
|
||||
first = false
|
||||
str += "["
|
||||
}
|
||||
str += strconv.Itoa(i)
|
||||
}
|
||||
}
|
||||
if str != "" {
|
||||
str += "]"
|
||||
}
|
||||
return str
|
||||
}
|
||||
@ -1,252 +0,0 @@
|
||||
// Copyright (c) 2024 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package configstatus_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConfigStatus_init_virgin(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "dummy.json")
|
||||
config, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "1.0.0", config.Data.Metadata.Version)
|
||||
|
||||
require.Equal(t, false, config.Data.DataV1.PendingSince.IsZero())
|
||||
require.Equal(t, true, config.Data.DataV1.LastProgress.IsZero())
|
||||
|
||||
require.Equal(t, "", config.Data.DataV1.Autoconf)
|
||||
require.Equal(t, uint64(0), config.Data.DataV1.ClickedLink)
|
||||
require.Equal(t, false, config.Data.DataV1.ReportSent)
|
||||
require.Equal(t, false, config.Data.DataV1.ReportClick)
|
||||
require.Equal(t, "", config.Data.DataV1.FailureDetails)
|
||||
}
|
||||
|
||||
func TestConfigStatus_init_existing(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "dummy.json")
|
||||
var data = configstatus.ConfigurationStatusData{
|
||||
Metadata: configstatus.Metadata{Version: "1.0.0"},
|
||||
DataV1: configstatus.DataV1{Autoconf: "Mr TBird"},
|
||||
}
|
||||
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
||||
|
||||
config, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "1.0.0", config.Data.Metadata.Version)
|
||||
require.Equal(t, "Mr TBird", config.Data.DataV1.Autoconf)
|
||||
}
|
||||
|
||||
func TestConfigStatus_init_bad_version(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "dummy.json")
|
||||
var data = configstatus.ConfigurationStatusData{
|
||||
Metadata: configstatus.Metadata{Version: "2.0.0"},
|
||||
DataV1: configstatus.DataV1{Autoconf: "Mr TBird"},
|
||||
}
|
||||
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
||||
|
||||
config, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "1.0.0", config.Data.Metadata.Version)
|
||||
require.Equal(t, "", config.Data.DataV1.Autoconf)
|
||||
}
|
||||
|
||||
func TestConfigStatus_IsPending(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "dummy.json")
|
||||
config, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, true, config.IsPending())
|
||||
config.Data.DataV1.PendingSince = time.Time{}
|
||||
require.Equal(t, false, config.IsPending())
|
||||
}
|
||||
|
||||
func TestConfigStatus_IsFromFailure(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "dummy.json")
|
||||
config, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, false, config.IsFromFailure())
|
||||
config.Data.DataV1.FailureDetails = "test"
|
||||
require.Equal(t, true, config.IsFromFailure())
|
||||
}
|
||||
|
||||
func TestConfigStatus_ApplySuccess(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "dummy.json")
|
||||
config, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, true, config.IsPending())
|
||||
require.NoError(t, config.ApplySuccess())
|
||||
require.Equal(t, false, config.IsPending())
|
||||
|
||||
config2, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
||||
require.Equal(t, true, config2.Data.DataV1.PendingSince.IsZero())
|
||||
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
|
||||
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
||||
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
|
||||
require.Equal(t, false, config2.Data.DataV1.ReportSent)
|
||||
require.Equal(t, false, config2.Data.DataV1.ReportClick)
|
||||
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
|
||||
}
|
||||
|
||||
func TestConfigStatus_ApplyFailure(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "dummy.json")
|
||||
config, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, config.ApplySuccess())
|
||||
|
||||
require.NoError(t, config.ApplyFailure("Big Failure"))
|
||||
require.Equal(t, true, config.IsFromFailure())
|
||||
require.Equal(t, true, config.IsPending())
|
||||
|
||||
config2, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
||||
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
|
||||
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
|
||||
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
||||
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
|
||||
require.Equal(t, false, config2.Data.DataV1.ReportSent)
|
||||
require.Equal(t, false, config2.Data.DataV1.ReportClick)
|
||||
require.Equal(t, "Big Failure", config2.Data.DataV1.FailureDetails)
|
||||
}
|
||||
|
||||
func TestConfigStatus_ApplyProgress(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "dummy.json")
|
||||
config, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, true, config.IsPending())
|
||||
require.Equal(t, true, config.Data.DataV1.LastProgress.IsZero())
|
||||
|
||||
require.NoError(t, config.ApplyProgress())
|
||||
|
||||
config2, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
||||
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
|
||||
require.Equal(t, false, config2.Data.DataV1.LastProgress.IsZero())
|
||||
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
||||
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
|
||||
require.Equal(t, false, config2.Data.DataV1.ReportSent)
|
||||
require.Equal(t, false, config2.Data.DataV1.ReportClick)
|
||||
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
|
||||
}
|
||||
|
||||
func TestConfigStatus_RecordLinkClicked(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "dummy.json")
|
||||
config, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, uint64(0), config.Data.DataV1.ClickedLink)
|
||||
require.NoError(t, config.RecordLinkClicked(0))
|
||||
require.Equal(t, uint64(1), config.Data.DataV1.ClickedLink)
|
||||
require.NoError(t, config.RecordLinkClicked(1))
|
||||
require.Equal(t, uint64(3), config.Data.DataV1.ClickedLink)
|
||||
|
||||
config2, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
||||
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
|
||||
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
|
||||
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
||||
require.Equal(t, uint64(3), config2.Data.DataV1.ClickedLink)
|
||||
require.Equal(t, false, config2.Data.DataV1.ReportSent)
|
||||
require.Equal(t, false, config2.Data.DataV1.ReportClick)
|
||||
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
|
||||
}
|
||||
|
||||
func TestConfigStatus_ReportClicked(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "dummy.json")
|
||||
config, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, false, config.Data.DataV1.ReportClick)
|
||||
require.NoError(t, config.ReportClicked())
|
||||
require.Equal(t, true, config.Data.DataV1.ReportClick)
|
||||
|
||||
config2, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
||||
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
|
||||
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
|
||||
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
||||
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
|
||||
require.Equal(t, false, config2.Data.DataV1.ReportSent)
|
||||
require.Equal(t, true, config2.Data.DataV1.ReportClick)
|
||||
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
|
||||
}
|
||||
|
||||
func TestConfigStatus_ReportSent(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "dummy.json")
|
||||
config, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, false, config.Data.DataV1.ReportSent)
|
||||
require.NoError(t, config.ReportSent())
|
||||
require.Equal(t, true, config.Data.DataV1.ReportSent)
|
||||
|
||||
config2, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
||||
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
|
||||
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
|
||||
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
||||
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
|
||||
require.Equal(t, true, config2.Data.DataV1.ReportSent)
|
||||
require.Equal(t, false, config2.Data.DataV1.ReportClick)
|
||||
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
|
||||
}
|
||||
|
||||
func dumpConfigStatusInFile(data *configstatus.ConfigurationStatusData, file string) error {
|
||||
f, err := os.Create(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
return json.NewEncoder(f).Encode(data)
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
// Copyright (c) 2024 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package configstatus
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ConfigAbortValues struct {
|
||||
Duration int `json:"duration"`
|
||||
}
|
||||
|
||||
type ConfigAbortDimensions struct {
|
||||
ReportClick string `json:"report_click"`
|
||||
ReportSent string `json:"report_sent"`
|
||||
ClickedLink string `json:"clicked_link"`
|
||||
}
|
||||
|
||||
type ConfigAbortData struct {
|
||||
MeasurementGroup string
|
||||
Event string
|
||||
Values ConfigSuccessValues
|
||||
Dimensions ConfigSuccessDimensions
|
||||
}
|
||||
|
||||
type ConfigAbortBuilder struct{}
|
||||
|
||||
func (*ConfigAbortBuilder) New(config *ConfigurationStatus) ConfigAbortData {
|
||||
config.DataLock.RLock()
|
||||
defer config.DataLock.RUnlock()
|
||||
|
||||
return ConfigAbortData{
|
||||
MeasurementGroup: "bridge.any.configuration",
|
||||
Event: "bridge_config_abort",
|
||||
Values: ConfigSuccessValues{
|
||||
Duration: config.isPendingSinceMin(),
|
||||
},
|
||||
Dimensions: ConfigSuccessDimensions{
|
||||
ReportClick: strconv.FormatBool(config.Data.DataV1.ReportClick),
|
||||
ReportSent: strconv.FormatBool(config.Data.DataV1.ReportSent),
|
||||
ClickedLink: config.Data.clickedLinkToString(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -1,75 +0,0 @@
|
||||
// Copyright (c) 2024 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package configstatus_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConfigurationAbort_default(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "dummy.json")
|
||||
config, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
var builder = configstatus.ConfigAbortBuilder{}
|
||||
req := builder.New(config)
|
||||
|
||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||
require.Equal(t, "bridge_config_abort", req.Event)
|
||||
require.Equal(t, 0, req.Values.Duration)
|
||||
require.Equal(t, "false", req.Dimensions.ReportClick)
|
||||
require.Equal(t, "false", req.Dimensions.ReportSent)
|
||||
require.Equal(t, "", req.Dimensions.ClickedLink)
|
||||
}
|
||||
|
||||
func TestConfigurationAbort_fed(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "dummy.json")
|
||||
var data = configstatus.ConfigurationStatusData{
|
||||
Metadata: configstatus.Metadata{Version: "1.0.0"},
|
||||
DataV1: configstatus.DataV1{
|
||||
PendingSince: time.Now().Add(-10 * time.Minute),
|
||||
LastProgress: time.Time{},
|
||||
Autoconf: "Mr TBird",
|
||||
ClickedLink: 42,
|
||||
ReportSent: false,
|
||||
ReportClick: true,
|
||||
FailureDetails: "Not an error",
|
||||
},
|
||||
}
|
||||
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
||||
|
||||
config, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
var builder = configstatus.ConfigAbortBuilder{}
|
||||
req := builder.New(config)
|
||||
|
||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||
require.Equal(t, "bridge_config_abort", req.Event)
|
||||
require.Equal(t, 10, req.Values.Duration)
|
||||
require.Equal(t, "true", req.Dimensions.ReportClick)
|
||||
require.Equal(t, "false", req.Dimensions.ReportSent)
|
||||
require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink)
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
// Copyright (c) 2024 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package configstatus
|
||||
|
||||
import "time"
|
||||
|
||||
type ConfigProgressValues struct {
|
||||
NbDay int `json:"nb_day"`
|
||||
NbDaySinceLast int `json:"nb_day_since_last"`
|
||||
}
|
||||
|
||||
type ConfigProgressData struct {
|
||||
MeasurementGroup string
|
||||
Event string
|
||||
Values ConfigProgressValues
|
||||
Dimensions struct{}
|
||||
}
|
||||
|
||||
type ConfigProgressBuilder struct{}
|
||||
|
||||
func (*ConfigProgressBuilder) New(config *ConfigurationStatus) ConfigProgressData {
|
||||
config.DataLock.RLock()
|
||||
defer config.DataLock.RUnlock()
|
||||
|
||||
return ConfigProgressData{
|
||||
MeasurementGroup: "bridge.any.configuration",
|
||||
Event: "bridge_config_progress",
|
||||
Values: ConfigProgressValues{
|
||||
NbDay: numberOfDay(time.Now(), config.Data.DataV1.PendingSince),
|
||||
NbDaySinceLast: numberOfDay(time.Now(), config.Data.DataV1.LastProgress),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func numberOfDay(now, prev time.Time) int {
|
||||
if now.IsZero() || prev.IsZero() {
|
||||
return 1
|
||||
}
|
||||
if now.Year() > prev.Year() {
|
||||
return (365 * (now.Year() - prev.Year())) + now.YearDay() - prev.YearDay()
|
||||
} else if now.YearDay() > prev.YearDay() {
|
||||
return now.YearDay() - prev.YearDay()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@ -1,100 +0,0 @@
|
||||
// Copyright (c) 2024 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package configstatus_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConfigurationProgress_default(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "dummy.json")
|
||||
config, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
var builder = configstatus.ConfigProgressBuilder{}
|
||||
req := builder.New(config)
|
||||
|
||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||
require.Equal(t, "bridge_config_progress", req.Event)
|
||||
require.Equal(t, 0, req.Values.NbDay)
|
||||
require.Equal(t, 1, req.Values.NbDaySinceLast)
|
||||
}
|
||||
|
||||
func TestConfigurationProgress_fed(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "dummy.json")
|
||||
var data = configstatus.ConfigurationStatusData{
|
||||
Metadata: configstatus.Metadata{Version: "1.0.0"},
|
||||
DataV1: configstatus.DataV1{
|
||||
PendingSince: time.Now().AddDate(0, 0, -5),
|
||||
LastProgress: time.Now().AddDate(0, 0, -2),
|
||||
Autoconf: "Mr TBird",
|
||||
ClickedLink: 42,
|
||||
ReportSent: false,
|
||||
ReportClick: true,
|
||||
FailureDetails: "Not an error",
|
||||
},
|
||||
}
|
||||
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
||||
|
||||
config, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
var builder = configstatus.ConfigProgressBuilder{}
|
||||
req := builder.New(config)
|
||||
|
||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||
require.Equal(t, "bridge_config_progress", req.Event)
|
||||
require.Equal(t, 5, req.Values.NbDay)
|
||||
require.Equal(t, 2, req.Values.NbDaySinceLast)
|
||||
}
|
||||
|
||||
func TestConfigurationProgress_fed_year_change(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "dummy.json")
|
||||
var data = configstatus.ConfigurationStatusData{
|
||||
Metadata: configstatus.Metadata{Version: "1.0.0"},
|
||||
DataV1: configstatus.DataV1{
|
||||
PendingSince: time.Now().AddDate(-1, 0, -5),
|
||||
LastProgress: time.Now().AddDate(0, 0, -2),
|
||||
Autoconf: "Mr TBird",
|
||||
ClickedLink: 42,
|
||||
ReportSent: false,
|
||||
ReportClick: true,
|
||||
FailureDetails: "Not an error",
|
||||
},
|
||||
}
|
||||
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
||||
|
||||
config, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
var builder = configstatus.ConfigProgressBuilder{}
|
||||
req := builder.New(config)
|
||||
|
||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||
require.Equal(t, "bridge_config_progress", req.Event)
|
||||
require.True(t, (req.Values.NbDay == 370) || (req.Values.NbDay == 371)) // leap year is accounted for in the simplest manner.
|
||||
require.Equal(t, 2, req.Values.NbDaySinceLast)
|
||||
}
|
||||
@ -1,63 +0,0 @@
|
||||
// Copyright (c) 2024 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package configstatus
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ConfigRecoveryValues struct {
|
||||
Duration int `json:"duration"`
|
||||
}
|
||||
|
||||
type ConfigRecoveryDimensions struct {
|
||||
Autoconf string `json:"autoconf"`
|
||||
ReportClick string `json:"report_click"`
|
||||
ReportSent string `json:"report_sent"`
|
||||
ClickedLink string `json:"clicked_link"`
|
||||
FailureDetails string `json:"failure_details"`
|
||||
}
|
||||
|
||||
type ConfigRecoveryData struct {
|
||||
MeasurementGroup string
|
||||
Event string
|
||||
Values ConfigRecoveryValues
|
||||
Dimensions ConfigRecoveryDimensions
|
||||
}
|
||||
|
||||
type ConfigRecoveryBuilder struct{}
|
||||
|
||||
func (*ConfigRecoveryBuilder) New(config *ConfigurationStatus) ConfigRecoveryData {
|
||||
config.DataLock.RLock()
|
||||
defer config.DataLock.RUnlock()
|
||||
|
||||
return ConfigRecoveryData{
|
||||
MeasurementGroup: "bridge.any.configuration",
|
||||
Event: "bridge_config_recovery",
|
||||
Values: ConfigRecoveryValues{
|
||||
Duration: config.isPendingSinceMin(),
|
||||
},
|
||||
Dimensions: ConfigRecoveryDimensions{
|
||||
Autoconf: config.Data.DataV1.Autoconf,
|
||||
ReportClick: strconv.FormatBool(config.Data.DataV1.ReportClick),
|
||||
ReportSent: strconv.FormatBool(config.Data.DataV1.ReportSent),
|
||||
ClickedLink: config.Data.clickedLinkToString(),
|
||||
FailureDetails: config.Data.DataV1.FailureDetails,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -1,79 +0,0 @@
|
||||
// Copyright (c) 2024 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package configstatus_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConfigurationRecovery_default(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "dummy.json")
|
||||
config, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
var builder = configstatus.ConfigRecoveryBuilder{}
|
||||
req := builder.New(config)
|
||||
|
||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||
require.Equal(t, "bridge_config_recovery", req.Event)
|
||||
require.Equal(t, 0, req.Values.Duration)
|
||||
require.Equal(t, "", req.Dimensions.Autoconf)
|
||||
require.Equal(t, "false", req.Dimensions.ReportClick)
|
||||
require.Equal(t, "false", req.Dimensions.ReportSent)
|
||||
require.Equal(t, "", req.Dimensions.ClickedLink)
|
||||
require.Equal(t, "", req.Dimensions.FailureDetails)
|
||||
}
|
||||
|
||||
func TestConfigurationRecovery_fed(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "dummy.json")
|
||||
var data = configstatus.ConfigurationStatusData{
|
||||
Metadata: configstatus.Metadata{Version: "1.0.0"},
|
||||
DataV1: configstatus.DataV1{
|
||||
PendingSince: time.Now().Add(-10 * time.Minute),
|
||||
LastProgress: time.Time{},
|
||||
Autoconf: "Mr TBird",
|
||||
ClickedLink: 42,
|
||||
ReportSent: false,
|
||||
ReportClick: true,
|
||||
FailureDetails: "Not an error",
|
||||
},
|
||||
}
|
||||
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
||||
|
||||
config, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
var builder = configstatus.ConfigRecoveryBuilder{}
|
||||
req := builder.New(config)
|
||||
|
||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||
require.Equal(t, "bridge_config_recovery", req.Event)
|
||||
require.Equal(t, 10, req.Values.Duration)
|
||||
require.Equal(t, "Mr TBird", req.Dimensions.Autoconf)
|
||||
require.Equal(t, "true", req.Dimensions.ReportClick)
|
||||
require.Equal(t, "false", req.Dimensions.ReportSent)
|
||||
require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink)
|
||||
require.Equal(t, "Not an error", req.Dimensions.FailureDetails)
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
// Copyright (c) 2024 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package configstatus
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ConfigSuccessValues struct {
|
||||
Duration int `json:"duration"`
|
||||
}
|
||||
|
||||
type ConfigSuccessDimensions struct {
|
||||
Autoconf string `json:"autoconf"`
|
||||
ReportClick string `json:"report_click"`
|
||||
ReportSent string `json:"report_sent"`
|
||||
ClickedLink string `json:"clicked_link"`
|
||||
}
|
||||
|
||||
type ConfigSuccessData struct {
|
||||
MeasurementGroup string
|
||||
Event string
|
||||
Values ConfigSuccessValues
|
||||
Dimensions ConfigSuccessDimensions
|
||||
}
|
||||
|
||||
type ConfigSuccessBuilder struct{}
|
||||
|
||||
func (*ConfigSuccessBuilder) New(config *ConfigurationStatus) ConfigSuccessData {
|
||||
config.DataLock.RLock()
|
||||
defer config.DataLock.RUnlock()
|
||||
|
||||
return ConfigSuccessData{
|
||||
MeasurementGroup: "bridge.any.configuration",
|
||||
Event: "bridge_config_success",
|
||||
Values: ConfigSuccessValues{
|
||||
Duration: config.isPendingSinceMin(),
|
||||
},
|
||||
Dimensions: ConfigSuccessDimensions{
|
||||
Autoconf: config.Data.DataV1.Autoconf,
|
||||
ReportClick: strconv.FormatBool(config.Data.DataV1.ReportClick),
|
||||
ReportSent: strconv.FormatBool(config.Data.DataV1.ReportSent),
|
||||
ClickedLink: config.Data.clickedLinkToString(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -1,77 +0,0 @@
|
||||
// Copyright (c) 2024 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package configstatus_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConfigurationSuccess_default(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "dummy.json")
|
||||
config, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
var builder = configstatus.ConfigSuccessBuilder{}
|
||||
req := builder.New(config)
|
||||
|
||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||
require.Equal(t, "bridge_config_success", req.Event)
|
||||
require.Equal(t, 0, req.Values.Duration)
|
||||
require.Equal(t, "", req.Dimensions.Autoconf)
|
||||
require.Equal(t, "false", req.Dimensions.ReportClick)
|
||||
require.Equal(t, "false", req.Dimensions.ReportSent)
|
||||
require.Equal(t, "", req.Dimensions.ClickedLink)
|
||||
}
|
||||
|
||||
func TestConfigurationSuccess_fed(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "dummy.json")
|
||||
var data = configstatus.ConfigurationStatusData{
|
||||
Metadata: configstatus.Metadata{Version: "1.0.0"},
|
||||
DataV1: configstatus.DataV1{
|
||||
PendingSince: time.Now().Add(-10 * time.Minute),
|
||||
LastProgress: time.Time{},
|
||||
Autoconf: "Mr TBird",
|
||||
ClickedLink: 42,
|
||||
ReportSent: false,
|
||||
ReportClick: true,
|
||||
FailureDetails: "Not an error",
|
||||
},
|
||||
}
|
||||
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
||||
|
||||
config, err := configstatus.LoadConfigurationStatus(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
var builder = configstatus.ConfigSuccessBuilder{}
|
||||
req := builder.New(config)
|
||||
|
||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||
require.Equal(t, "bridge_config_success", req.Event)
|
||||
require.Equal(t, 10, req.Values.Duration)
|
||||
require.Equal(t, "Mr TBird", req.Dimensions.Autoconf)
|
||||
require.Equal(t, "true", req.Dimensions.ReportClick)
|
||||
require.Equal(t, "false", req.Dimensions.ReportSent)
|
||||
require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink)
|
||||
}
|
||||
@ -1,56 +0,0 @@
|
||||
// Copyright (c) 2024 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package configstatus
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||
)
|
||||
|
||||
const ProgressCheckInterval = time.Hour
|
||||
|
||||
type Metadata struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type MetadataOnly struct {
|
||||
Metadata Metadata `json:"metadata"`
|
||||
}
|
||||
|
||||
type DataV1 struct {
|
||||
PendingSince time.Time `json:"pending_since"`
|
||||
LastProgress time.Time `json:"last_progress"`
|
||||
Autoconf string `json:"auto_conf"`
|
||||
ClickedLink uint64 `json:"clicked_link"`
|
||||
ReportSent bool `json:"report_sent"`
|
||||
ReportClick bool `json:"report_click"`
|
||||
FailureDetails string `json:"failure_details"`
|
||||
}
|
||||
|
||||
type ConfigurationStatusData struct {
|
||||
Metadata Metadata `json:"metadata"`
|
||||
DataV1 DataV1 `json:"dataV1"`
|
||||
}
|
||||
|
||||
type ConfigurationStatus struct {
|
||||
FilePath string
|
||||
DataLock safe.RWMutex
|
||||
|
||||
Data *ConfigurationStatusData
|
||||
}
|
||||
@ -66,7 +66,7 @@ const (
|
||||
KeyChainName = "bridge-v3"
|
||||
|
||||
// Host is the hostname of the bridge server.
|
||||
Host = "127.0.0.1"
|
||||
Host = "0.0.0.0"
|
||||
)
|
||||
|
||||
// nolint:goconst
|
||||
|
||||
@ -30,7 +30,7 @@ using namespace bridgepp;
|
||||
namespace {
|
||||
|
||||
QString const defaultKeychain = "defaultKeychain"; ///< The default keychain.
|
||||
QString const HV_ERROR_TEMPLATE = "failed to create new API client: 422 POST https://mail-api.proton.me/auth/v4: CAPTCHA validation failed (Code=12087, Status=422)";
|
||||
QString const HV_ERROR_TEMPLATE = "Human verification failed. Please try again.";
|
||||
|
||||
}
|
||||
|
||||
@ -364,9 +364,9 @@ grpc::Status GRPCService::RequestKnowledgeBaseSuggestions(ServerContext*, String
|
||||
|
||||
QList<bridgepp::KnowledgeBaseSuggestion> suggestions;
|
||||
for (qsizetype i = 1; i <= 3; ++i) {
|
||||
suggestions.push_back( {
|
||||
.title = QString("Suggested link %1").arg(i),
|
||||
suggestions.push_back({
|
||||
.url = QString("https://proton.me/support/bridge#%1").arg(i),
|
||||
.title = QString("Suggested link %1").arg(i),
|
||||
});
|
||||
}
|
||||
qtProxy_.sendDelayedEvent(newKnowledgeBaseSuggestionsEvent(app().mainWindow().knowledgeBaseTab().getSuggestions()));
|
||||
@ -846,32 +846,6 @@ Status GRPCService::InstallTLSCertificate(ServerContext *, Empty const *, Empty
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] request The request.
|
||||
//****************************************************************************************************************************************************
|
||||
Status GRPCService::ExternalLinkClicked(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) {
|
||||
app().log().debug(QString("%1 - URL = %2").arg(__FUNCTION__, QString::fromStdString(request->value())));
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
Status GRPCService::ReportBugClicked(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::Empty *) {
|
||||
app().log().debug(__FUNCTION__);
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] request The request.
|
||||
//****************************************************************************************************************************************************
|
||||
Status GRPCService::AutoconfigClicked(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *response) {
|
||||
app().log().debug(QString("%1 - Client = %2").arg(__FUNCTION__, QString::fromStdString(request->value())));
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] request The request
|
||||
/// \param[in] writer The writer
|
||||
|
||||
@ -97,9 +97,6 @@ public: // member functions.
|
||||
grpc::Status IsTLSCertificateInstalled(::grpc::ServerContext *, ::google::protobuf::Empty const*, ::google::protobuf::BoolValue *response) override;
|
||||
grpc::Status 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 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 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.
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
#include <bridgepp/GRPC/GRPCClient.h>
|
||||
#include <bridgepp/Worker/Overseer.h>
|
||||
|
||||
#include "Settings.h"
|
||||
|
||||
#define HANDLE_EXCEPTION(x) try { x } \
|
||||
catch (Exception const &e) { emit fatalError(e); } \
|
||||
@ -59,15 +60,19 @@ QMLBackend::QMLBackend()
|
||||
/// \param[in] serviceConfig
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::init(GRPCConfig const &serviceConfig) {
|
||||
Log &log = app().log();
|
||||
log.info(QString("Connecting to gRPC service"));
|
||||
|
||||
trayIcon_.reset(new TrayIcon());
|
||||
connect(this, &QMLBackend::trayIconVisibleChanged, trayIcon_.get(), &TrayIcon::setVisible);
|
||||
log.info(QString("Tray icon is visible: %1").arg(trayIcon_->isVisible() ? "true" : "false"));
|
||||
this->setNormalTrayIcon();
|
||||
|
||||
|
||||
connect(this, &QMLBackend::fatalError, &app(), &AppController::onFatalError);
|
||||
|
||||
users_ = new UserList(this);
|
||||
|
||||
Log &log = app().log();
|
||||
log.info(QString("Connecting to gRPC service"));
|
||||
app().grpc().setLog(&log);
|
||||
this->connectGrpcEvents();
|
||||
|
||||
@ -298,7 +303,6 @@ void QMLBackend::openExternalLink(QString const &url) {
|
||||
HANDLE_EXCEPTION(
|
||||
QString const u = url.isEmpty() ? bridgeKBUrl : url;
|
||||
QDesktopServices::openUrl(u);
|
||||
emit notifyExternalLinkClicked(u);
|
||||
)
|
||||
}
|
||||
|
||||
@ -731,6 +735,32 @@ void QMLBackend::setDockIconVisible(bool visible) {
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] visible Should the tray icon be visible.
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::setTrayIconVisible(bool visible) {
|
||||
HANDLE_EXCEPTION(
|
||||
AppController& app = ::app();
|
||||
if (visible == app.settings().trayIconVisible()) {
|
||||
return;
|
||||
}
|
||||
app.settings().setTrayIconVisible(visible);
|
||||
emit trayIconVisibleChanged(visible);
|
||||
app.log().info(QString("Changing tray icon visibility to %1").arg(visible ? "true" : "false"));
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
bool QMLBackend::trayIconVisible() const {
|
||||
HANDLE_EXCEPTION_RETURN_BOOL(
|
||||
return app().settings().trayIconVisible();
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] active Should we activate autostart.
|
||||
//****************************************************************************************************************************************************
|
||||
@ -1064,33 +1094,6 @@ void QMLBackend::sendBadEventUserFeedback(QString const &userID, bool doResync)
|
||||
)
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
///
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::notifyReportBugClicked() const {
|
||||
HANDLE_EXCEPTION(
|
||||
app().grpc().reportBugClicked();
|
||||
)
|
||||
}
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] client The selected Mail client for autoconfig.
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::notifyAutoconfigClicked(QString const &client) const {
|
||||
HANDLE_EXCEPTION(
|
||||
app().grpc().autoconfigClicked(client);
|
||||
)
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] article The url of the KB article.
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::notifyExternalLinkClicked(QString const &article) const {
|
||||
HANDLE_EXCEPTION(
|
||||
app().grpc().externalLinkClicked(article);
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
|
||||
@ -102,6 +102,7 @@ public: // Qt/QML properties. Note that the NOTIFY-er signal is required even fo
|
||||
Q_PROPERTY(QVariantList bugQuestions READ bugQuestions NOTIFY bugQuestionsChanged)
|
||||
Q_PROPERTY(UserList *users MEMBER users_ NOTIFY usersChanged)
|
||||
Q_PROPERTY(bool dockIconVisible READ dockIconVisible WRITE setDockIconVisible NOTIFY dockIconVisibleChanged)
|
||||
Q_PROPERTY(bool trayIconVisible READ trayIconVisible WRITE setTrayIconVisible NOTIFY trayIconVisibleChanged)
|
||||
|
||||
// Qt Property system setters & getters.
|
||||
bool showOnStartup() const; ///< Getter for the 'showOnStartup' property.
|
||||
@ -141,6 +142,9 @@ public: // Qt/QML properties. Note that the NOTIFY-er signal is required even fo
|
||||
QVariantList bugQuestions() const; ///< Getter for the 'bugQuestions' property.
|
||||
void setDockIconVisible(bool visible); ///< Setter for the 'dockIconVisible' property.
|
||||
bool dockIconVisible() const;; ///< Getter for the 'dockIconVisible' property.
|
||||
void setTrayIconVisible(bool visible); ///< Setter for the 'trayIconVisible' property.
|
||||
bool trayIconVisible() const; ///< Getter for the 'trayIconVisible' property.
|
||||
|
||||
|
||||
signals: // Signal used by the Qt property system. Many of them are unused but required to avoid warning from the QML engine.
|
||||
void showSplashScreenChanged(bool value); ///<Signal for the change of the 'showSplashScreen' property.
|
||||
@ -175,6 +179,7 @@ signals: // Signal used by the Qt property system. Many of them are unused but r
|
||||
void isAutostartOnChanged(bool value); ///<Signal for the change of the 'isAutostartOn' property.
|
||||
void usersChanged(UserList *users); ///<Signal for the change of the 'users' property.
|
||||
void dockIconVisibleChanged(bool value); ///<Signal for the change of the 'dockIconVisible' property.
|
||||
void trayIconVisibleChanged(bool value); ///< Signal for the change of the 'trayIconVisible' property.
|
||||
void receivedUserNotification(bridgepp::UserNotification const& notification); ///< Signal to display the userNotification modal
|
||||
|
||||
|
||||
@ -208,9 +213,6 @@ public slots: // slot for signals received from QML -> To be forwarded to Bridge
|
||||
void onVersionChanged(); ///< Slot for the version change signal.
|
||||
void 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 notifyReportBugClicked() const; ///< Slot for the ReportBugClicked gRPC event.
|
||||
void notifyAutoconfigClicked(QString const &client) const; ///< Slot for gAutoconfigClicked gRPC event.
|
||||
void notifyExternalLinkClicked(QString const &article) const; ///< Slot for KBArticleClicked gRPC event.
|
||||
void triggerRepair() const; ///< Slot for the triggering of the bridge repair function i.e. 'resync'.
|
||||
void userNotificationDismissed(); ///< Slot to pop the notification from the stack and display the rest.
|
||||
|
||||
|
||||
@ -28,6 +28,7 @@ namespace {
|
||||
|
||||
QString const settingsFileName = "bridge-gui.ini"; ///< The name of the settings file.
|
||||
QString const keyUseSoftwareRenderer = "UseSoftwareRenderer"; ///< The key for storing the 'Use software rendering' setting.
|
||||
QString const keyTrayIconVisible = "TrayIconVisible"; ///< The key for storing the 'Tray icon visible' setting.
|
||||
|
||||
|
||||
}
|
||||
@ -36,7 +37,7 @@ QString const keyUseSoftwareRenderer = "UseSoftwareRenderer"; ///< The key for s
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
Settings::Settings()
|
||||
: settings_(QDir(userConfigDir()).absoluteFilePath("bridge-gui.ini"), QSettings::Format::IniFormat) {
|
||||
: settings_(QDir(userConfigDir()).absoluteFilePath(settingsFileName), QSettings::Format::IniFormat) {
|
||||
}
|
||||
|
||||
|
||||
@ -54,3 +55,17 @@ bool Settings::useSoftwareRenderer() const {
|
||||
void Settings::setUseSoftwareRenderer(bool value) {
|
||||
settings_.setValue(keyUseSoftwareRenderer, value);
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] value The value for the 'Tray icon visible' setting.
|
||||
//****************************************************************************************************************************************************
|
||||
void Settings::setTrayIconVisible(bool value) {
|
||||
settings_.setValue(keyTrayIconVisible, value);
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The value for the 'Tray icon visible' setting.
|
||||
//****************************************************************************************************************************************************
|
||||
bool Settings::trayIconVisible() const {
|
||||
return settings_.value(keyTrayIconVisible, true).toBool();
|
||||
}
|
||||
|
||||
@ -33,6 +33,8 @@ public: // member functions.
|
||||
|
||||
bool useSoftwareRenderer() const; ///< Get the 'Use software renderer' settings value.
|
||||
void setUseSoftwareRenderer(bool value); ///< Set the 'Use software renderer' settings value.
|
||||
void setTrayIconVisible(bool value); ///< Get the 'Tray icon visible' setting value.
|
||||
bool trayIconVisible() const; ///< Set the 'Tray icon visible' setting value.
|
||||
|
||||
private: // member functions.
|
||||
Settings(); ///< Default constructor.
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
#include <bridgepp/Exception/Exception.h>
|
||||
#include <bridgepp/BridgeUtils.h>
|
||||
|
||||
#include "Settings.h"
|
||||
|
||||
using namespace bridgepp;
|
||||
|
||||
@ -195,7 +196,7 @@ TrayIcon::TrayIcon()
|
||||
}
|
||||
|
||||
this->setIcon();
|
||||
this->show();
|
||||
this->setVisible(app().settings().trayIconVisible());
|
||||
|
||||
// TrayIcon does not expose its screen, so we connect relevant screen events to our DPI change handler.
|
||||
for (QScreen *screen: QGuiApplication::screens()) {
|
||||
@ -209,7 +210,6 @@ TrayIcon::TrayIcon()
|
||||
connect(&iconRefreshTimer_, &QTimer::timeout, this, &TrayIcon::onIconRefreshTimer);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
@ -322,21 +322,38 @@ void TrayIcon::setState(TrayIcon::State state, QString const &stateString, QStri
|
||||
this->generateStatusIcon(statusIconPath, stateColor(state));
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief A helper struct to temporarily force the tray to be visible. Useful for operations that do not work when the tray is not visible,
|
||||
/// such as showMessage().
|
||||
//****************************************************************************************************************************************************
|
||||
struct ScopedTrayVisibility {
|
||||
explicit ScopedTrayVisibility(TrayIcon& trayIcon) : trayIcon_(trayIcon), wasVisible_(app().settings().trayIconVisible()) {
|
||||
trayIcon_.setVisible(true);
|
||||
}
|
||||
~ScopedTrayVisibility() { trayIcon_.setVisible(wasVisible_); }
|
||||
|
||||
private:
|
||||
TrayIcon &trayIcon_;
|
||||
bool wasVisible_;
|
||||
};
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] title The title.
|
||||
/// \param[in] message The message.
|
||||
//****************************************************************************************************************************************************
|
||||
void TrayIcon::showErrorPopupNotification(QString const &title, QString const &message) {
|
||||
ScopedTrayVisibility visible(*this);
|
||||
this->showMessage(title, message, notificationErrorIcon_);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// Used only by user notifications received from the event loop
|
||||
/// \param[in] title The title.
|
||||
/// \param[in] subtitle The subtitle.
|
||||
//****************************************************************************************************************************************************
|
||||
void TrayIcon::showUserNotification(QString const &title, QString const &subtitle) {
|
||||
ScopedTrayVisibility visible(*this);
|
||||
this->showMessage(title, subtitle, QSystemTrayIcon::NoIcon);
|
||||
}
|
||||
|
||||
|
||||
@ -43,8 +43,6 @@ public: // data members
|
||||
void setState(State state, QString const& stateString, QString const &statusIconPath); ///< Set the state of the icon
|
||||
void showErrorPopupNotification(QString const& title, QString const &message); ///< Display a pop up notification.
|
||||
void showUserNotification(QString const& title, QString const &subtitle); ///< Display an OS pop up notification (without icon).
|
||||
|
||||
|
||||
signals:
|
||||
void selectUser(QString const& userID, bool forceShowWindow); ///< Signal for selecting a user with a given userID
|
||||
|
||||
|
||||
@ -86,11 +86,13 @@ Item {
|
||||
}
|
||||
}
|
||||
Button {
|
||||
id: signIn
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
secondary: true
|
||||
text: qsTr("Sign in")
|
||||
visible: root.user ? (root.user.state === EUserState.SignedOut) : false
|
||||
Accessible.name: text
|
||||
|
||||
onClicked: {
|
||||
if (user) {
|
||||
@ -99,11 +101,13 @@ Item {
|
||||
}
|
||||
}
|
||||
Button {
|
||||
id: removeAccount
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
icon.source: "/qml/icons/ic-trash.svg"
|
||||
secondary: true
|
||||
visible: root.user ? root.user.state !== EUserState.Locked : false
|
||||
Accessible.name: qsTr("Remove account")
|
||||
|
||||
onClicked: {
|
||||
if (!root.user)
|
||||
@ -118,6 +122,7 @@ Item {
|
||||
height: root._lineThickness
|
||||
}
|
||||
SettingsItem {
|
||||
id: configureEmailClient
|
||||
Layout.fillWidth: true
|
||||
actionText: qsTr("Configure email client")
|
||||
colorScheme: root.colorScheme
|
||||
@ -126,6 +131,7 @@ Item {
|
||||
text: qsTr("Email clients")
|
||||
type: SettingsItem.PrimaryButton
|
||||
visible: _connected && ((!root.user.splitMode) || (root.user.addresses.length === 1))
|
||||
Accessible.name: actionText
|
||||
|
||||
onClicked: {
|
||||
if (!root.user)
|
||||
@ -143,6 +149,7 @@ Item {
|
||||
text: qsTr("Split addresses")
|
||||
type: SettingsItem.Toggle
|
||||
visible: _connected && root.user.addresses.length > 1
|
||||
Accessible.name: text
|
||||
|
||||
onClicked: {
|
||||
if (!splitMode.checked) {
|
||||
|
||||
@ -28,7 +28,7 @@ Popup {
|
||||
implicitWidth: 600 // contentLayout.implicitWidth + contentLayout.anchors.leftMargin + contentLayout.anchors.rightMargin
|
||||
leftMargin: (mainWindow.width - root.implicitWidth) / 2
|
||||
modal: false
|
||||
popupType: ApplicationWindow.PopupType.Banner
|
||||
popupPriority: ApplicationWindow.PopupPriority.Banner
|
||||
shouldShow: notification ? (notification.active && !notification.dismissed) : false
|
||||
topMargin: 37
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.impl
|
||||
import Proton
|
||||
|
||||
Item {
|
||||
|
||||
@ -114,6 +114,7 @@ Item {
|
||||
colorScheme: leftBar.colorScheme
|
||||
horizontalPadding: 0
|
||||
icon.source: "/qml/icons/ic-question-circle.svg"
|
||||
Accessible.name: qsTr("Help")
|
||||
|
||||
onClicked: rightContent.showHelpView()
|
||||
}
|
||||
@ -130,6 +131,7 @@ Item {
|
||||
colorScheme: leftBar.colorScheme
|
||||
horizontalPadding: 0
|
||||
icon.source: "/qml/icons/ic-cog-wheel.svg"
|
||||
Accessible.name: qsTr("Settings")
|
||||
|
||||
onClicked: rightContent.showGeneralSettings()
|
||||
}
|
||||
@ -147,6 +149,7 @@ Item {
|
||||
colorScheme: leftBar.colorScheme
|
||||
horizontalPadding: 0
|
||||
icon.source: "/qml/icons/ic-three-dots-vertical.svg"
|
||||
Accessible.name: "..."
|
||||
|
||||
onClicked: {
|
||||
dotMenu.open();
|
||||
@ -319,6 +322,7 @@ Item {
|
||||
horizontalPadding: 0
|
||||
icon.source: "/qml/icons/ic-plus.svg"
|
||||
width: 36
|
||||
Accessible.name: qsTr("Add account")
|
||||
|
||||
onClicked: {
|
||||
root.showLogin("");
|
||||
|
||||
@ -147,6 +147,19 @@ SettingsView {
|
||||
|
||||
onClicked: Backend.changeColorScheme(darkMode.checked ? "light" : "dark")
|
||||
}
|
||||
SettingsItem {
|
||||
id: trayIconVisible
|
||||
Layout.fillWidth: true
|
||||
checked: Backend.trayIconVisible
|
||||
colorScheme: root.colorScheme
|
||||
description: qsTr("Show the Bridge icon in the menu bar. When the Bridge icon is not visible, launch the " +
|
||||
"application again to display the main window.")
|
||||
text: qsTr("Show the Bridge icon in the menu bar")
|
||||
type: SettingsItem.Toggle
|
||||
visible: (Backend.goos === "darwin") && root._isAdvancedShown
|
||||
|
||||
onClicked: Backend.trayIconVisible = !trayIconVisible.checked
|
||||
}
|
||||
SettingsItem {
|
||||
id: allMail
|
||||
Layout.fillWidth: true
|
||||
|
||||
@ -83,7 +83,6 @@ SettingsView {
|
||||
|
||||
onClicked: {
|
||||
Backend.updateCurrentMailClient();
|
||||
Backend.notifyReportBugClicked();
|
||||
root.parent.showBugReport();
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ T.ApplicationWindow {
|
||||
id: root
|
||||
|
||||
// popup priority based on types
|
||||
enum PopupType {
|
||||
enum PopupPriority {
|
||||
Banner,
|
||||
Dialog
|
||||
}
|
||||
@ -78,10 +78,10 @@ T.ApplicationWindow {
|
||||
topmost = obj;
|
||||
break;
|
||||
}
|
||||
if (topmost && (topmost.popupType > obj.popupType)) {
|
||||
if (topmost && (topmost.popupPriority > obj.popupPriority)) {
|
||||
continue;
|
||||
}
|
||||
if (topmost && (topmost.popupType === obj.popupType) && (topmost.occurred > obj.occurred)) {
|
||||
if (topmost && (topmost.popupPriority === obj.popupPriority) && (topmost.occurred > obj.occurred)) {
|
||||
continue;
|
||||
}
|
||||
topmost = obj;
|
||||
|
||||
@ -21,7 +21,7 @@ T.Dialog {
|
||||
|
||||
property ColorScheme colorScheme
|
||||
readonly property var occurred: shouldShow ? new Date() : undefined
|
||||
readonly property int popupType: ApplicationWindow.PopupType.Dialog
|
||||
readonly property int popupPriority: ApplicationWindow.PopupPriority.Dialog
|
||||
property bool shouldShow: false
|
||||
|
||||
function close() {
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.impl
|
||||
import QtQuick.Layouts
|
||||
|
||||
ColorImage {
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.impl
|
||||
import QtQuick.Layouts
|
||||
|
||||
RowLayout {
|
||||
|
||||
@ -21,7 +21,7 @@ T.Popup {
|
||||
|
||||
property ColorScheme colorScheme
|
||||
readonly property var occurred: shouldShow ? new Date() : undefined
|
||||
property int popupType: ApplicationWindow.PopupType.Banner
|
||||
property int popupPriority: ApplicationWindow.PopupPriority.Banner
|
||||
property bool shouldShow: false
|
||||
|
||||
function close() {
|
||||
|
||||
@ -172,6 +172,8 @@ FocusScope {
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
Accessible.role: Accessible.Grouping
|
||||
Accessible.name: label.text
|
||||
|
||||
onEditingFinished: {
|
||||
if (!validateOnEditingFinished) {
|
||||
@ -274,6 +276,7 @@ FocusScope {
|
||||
selectionColor: control.palette.highlight
|
||||
topPadding: 8
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
Accessible.name: label.text + qsTr(" edit")
|
||||
|
||||
background: Item {
|
||||
implicitHeight: 36
|
||||
@ -349,6 +352,7 @@ FocusScope {
|
||||
icon.color: control.color
|
||||
icon.source: checked ? "../icons/ic-eye-slash.svg" : "../icons/ic-eye.svg"
|
||||
visible: root.echoMode === TextInput.Password
|
||||
Accessible.name: label.text + qsTr(" show check")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,6 +41,8 @@ Item {
|
||||
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
Accessible.name: text
|
||||
Accessible.role: Accessible.Grouping
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
@ -77,6 +79,8 @@ Item {
|
||||
colorScheme: root.colorScheme
|
||||
loading: root.loading
|
||||
visible: root.type === SettingsItem.ActionType.Toggle
|
||||
Accessible.role: Accessible.CheckBox
|
||||
Accessible.name: root.Accessible.name + " toggle"
|
||||
|
||||
onClicked: {
|
||||
if (!root.loading)
|
||||
@ -92,6 +96,8 @@ Item {
|
||||
secondary: root.type !== SettingsItem.PrimaryButton
|
||||
text: root.actionText
|
||||
visible: root.type === SettingsItem.Button || root.type === SettingsItem.PrimaryButton
|
||||
Accessible.role: Accessible.Button
|
||||
Accessible.name: root.Accessible.name + " button"
|
||||
|
||||
onClicked: {
|
||||
if (!root.loading)
|
||||
|
||||
@ -80,6 +80,7 @@ Item {
|
||||
horizontalPadding: 8
|
||||
icon.source: "/qml/icons/ic-arrow-left.svg"
|
||||
secondary: true
|
||||
Accessible.name: qsTr("Back")
|
||||
|
||||
onClicked: root.back()
|
||||
|
||||
|
||||
@ -51,7 +51,7 @@ Item {
|
||||
color: colorScheme.text_weak
|
||||
colorScheme: wizard.colorScheme
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: qsTr("A system pop-up will appear. Double click on the entry with your email, and click ’Install’ in the dialog that appears.")
|
||||
text: qsTr("A series of pop-ups will appear. Follow the instructions to install the profile.")
|
||||
type: Label.LabelType.Body
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ import QtQml
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.impl
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
@ -14,6 +14,7 @@ import QtQml
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.impl
|
||||
import ".."
|
||||
|
||||
Rectangle {
|
||||
|
||||
@ -14,6 +14,7 @@ import QtQml
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.impl
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
@ -37,6 +38,8 @@ Rectangle {
|
||||
}
|
||||
height: 68
|
||||
radius: ProtonStyle.banner_radius
|
||||
Accessible.role: Accessible.Button
|
||||
Accessible.name: root.text
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
@ -33,6 +33,7 @@ Button {
|
||||
icon.source: "/qml/icons/ic-question-circle.svg"
|
||||
icon.width: _iconSize
|
||||
verticalPadding: 0
|
||||
Accessible.name: qsTr("Help")
|
||||
|
||||
onClicked: {
|
||||
menu.popup(-menu.width + root.width, -menu.height);
|
||||
|
||||
@ -60,7 +60,7 @@ Item {
|
||||
}
|
||||
function showAppleMailAutoconfigProfileInstall() {
|
||||
showClientConfigCommon();
|
||||
descriptionLabel.text = qsTr("The final step before you can start using Apple Mail is to install the Bridge server profile in the system preferences.\n\nAdding a server profile is necessary to ensure that your Mac can receive and send Proton Mails.");
|
||||
descriptionLabel.text = qsTr("The final step before you can start using Apple Mail is to install the Bridge server profile in the system preferences.\n\nAdding a server profile is necessary to ensure that your Mac can receive and send Proton Mail messages.");
|
||||
linkLabel1.setCallback(function() { Backend.openExternalLink("https://proton.me/support/macos-certificate-warning"); }, qsTr("Why is there a yellow warning sign?"), true);
|
||||
linkLabel2.setCallback(wizard.showClientParams, qsTr("Configure Apple Mail manually"), false);
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ import QtQml
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.impl
|
||||
|
||||
FocusScope {
|
||||
id: root
|
||||
@ -28,6 +29,7 @@ FocusScope {
|
||||
property alias username: usernameTextField.text
|
||||
property var wizard
|
||||
property string hvLinkUrl: ""
|
||||
property bool hvLinkClicked: false
|
||||
|
||||
signal loginAbort(string username, bool wasSignedOut)
|
||||
|
||||
@ -48,6 +50,7 @@ FocusScope {
|
||||
}
|
||||
passwordTextField.hidePassword();
|
||||
secondPasswordTextField.hidePassword();
|
||||
hvLinkClicked = false;
|
||||
}
|
||||
function resetViaHv() {
|
||||
usernameTextField.enabled = false;
|
||||
@ -55,6 +58,7 @@ FocusScope {
|
||||
signInButton.loading = true;
|
||||
secondPasswordButton.loading = false;
|
||||
secondPasswordTextField.enabled = true;
|
||||
hvLinkClicked = false;
|
||||
totpLayout.reset();
|
||||
}
|
||||
|
||||
@ -561,6 +565,7 @@ FocusScope {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
Qt.openUrlExternally(hvLinkUrl);
|
||||
hvLinkClicked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -573,7 +578,8 @@ FocusScope {
|
||||
id: hVContinueButton
|
||||
Layout.fillWidth: true
|
||||
colorScheme: wizard.colorScheme
|
||||
text: qsTr("Continue")
|
||||
text: qsTr("I’ve completed the verification")
|
||||
enabled: hvLinkClicked
|
||||
|
||||
function checkAndSignInHv() {
|
||||
console.assert(stackLayout.currentIndex === Login.RootStack.HV || stackLayout.currentIndex === Login.RootStack.MailboxPassword, "Unexpected checkInAndSignInHv")
|
||||
|
||||
@ -1572,32 +1572,6 @@ UPClientContext GRPCClient::clientContext() const {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return the status for the gRPC call.
|
||||
//****************************************************************************************************************************************************
|
||||
grpc::Status GRPCClient::reportBugClicked() {
|
||||
return this->logGRPCCallStatus(stub_->ReportBugClicked(this->clientContext().get(), empty, &empty), __FUNCTION__);
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] client The client string.
|
||||
/// \return the status for the gRPC call.
|
||||
//****************************************************************************************************************************************************
|
||||
grpc::Status GRPCClient::autoconfigClicked(QString const &client) {
|
||||
StringValue s;
|
||||
s.set_value(client.toStdString());
|
||||
return this->logGRPCCallStatus(stub_->AutoconfigClicked(this->clientContext().get(), s, &empty), __FUNCTION__);
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] link The clicked link.
|
||||
/// \return the status for the gRPC call.
|
||||
//****************************************************************************************************************************************************
|
||||
grpc::Status GRPCClient::externalLinkClicked(QString const &link) {
|
||||
StringValue s;
|
||||
s.set_value(link.toStdString());
|
||||
return this->logGRPCCallStatus(stub_->ExternalLinkClicked(this->clientContext().get(), s, &empty), __FUNCTION__);
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
|
||||
@ -232,11 +232,6 @@ signals:
|
||||
void syncFinished(QString const &userID);
|
||||
void syncProgress(QString const &userID, double progress, qint64 elapsedMs, qint64 remainingMs);
|
||||
|
||||
public: // telemetry related calls
|
||||
grpc::Status reportBugClicked(); ///< Performs the 'reportBugClicked' call.
|
||||
grpc::Status autoconfigClicked(QString const &userID); ///< Performs the 'AutoconfigClicked' call.
|
||||
grpc::Status externalLinkClicked(QString const &userID); ///< Performs the 'KBArticleClicked' call.
|
||||
|
||||
public: // keychain related calls
|
||||
grpc::Status availableKeychains(QStringList &outKeychains);
|
||||
grpc::Status currentKeychain(QString &outKeychain);
|
||||
|
||||
@ -159,7 +159,10 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) {
|
||||
hvDetails, hvErr := hv.VerifyAndExtractHvRequest(err)
|
||||
if hvErr != nil || hvDetails != nil {
|
||||
if hvErr != nil {
|
||||
f.printAndLogError("Cannot login", hvErr)
|
||||
f.printAndLogError("Cannot login:", hv.ExtractionErrorMsg)
|
||||
f.bridge.ReportMessageWithContext("Unable to extract HV request details", map[string]any{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
f.promptHvURL(hvDetails)
|
||||
|
||||
@ -5425,7 +5425,7 @@ var file_bridge_proto_rawDesc = []byte{
|
||||
0x4c, 0x53, 0x5f, 0x43, 0x45, 0x52, 0x54, 0x5f, 0x45, 0x58, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x45,
|
||||
0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x4c, 0x53, 0x5f, 0x4b, 0x45,
|
||||
0x59, 0x5f, 0x45, 0x58, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02,
|
||||
0x32, 0x99, 0x23, 0x0a, 0x06, 0x42, 0x72, 0x69, 0x64, 0x67, 0x65, 0x12, 0x49, 0x0a, 0x0b, 0x43,
|
||||
0x32, 0xbd, 0x21, 0x0a, 0x06, 0x42, 0x72, 0x69, 0x64, 0x67, 0x65, 0x12, 0x49, 0x0a, 0x0b, 0x43,
|
||||
0x68, 0x65, 0x63, 0x6b, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72,
|
||||
0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
@ -5666,51 +5666,37 @@ var file_bridge_proto_rawDesc = []byte{
|
||||
0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x4d,
|
||||
0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,
|
||||
0x74, 0x79, 0x12, 0x42, 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x43,
|
||||
0x6c, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x49, 0x0a, 0x11, 0x41, 0x75, 0x74, 0x6f, 0x63, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x1c, 0x2e, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74,
|
||||
0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74,
|
||||
0x79, 0x12, 0x4b, 0x0a, 0x13, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4c, 0x69, 0x6e,
|
||||
0x6b, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e,
|
||||
0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4f,
|
||||
0x0a, 0x19, 0x49, 0x73, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
|
||||
0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
|
||||
0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12,
|
||||
0x47, 0x0a, 0x15, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72,
|
||||
0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
||||
0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4d, 0x0a, 0x15, 0x45, 0x78, 0x70, 0x6f,
|
||||
0x72, 0x74, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65,
|
||||
0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a,
|
||||
0x74, 0x79, 0x12, 0x4f, 0x0a, 0x19, 0x49, 0x73, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69,
|
||||
0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x12,
|
||||
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x0e, 0x52, 0x75, 0x6e, 0x45, 0x76,
|
||||
0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63,
|
||||
0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61,
|
||||
0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x0f, 0x53, 0x74, 0x6f, 0x70,
|
||||
0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x16, 0x2e, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
|
||||
0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x0d, 0x54,
|
||||
0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x70, 0x61, 0x69, 0x72, 0x12, 0x16, 0x2e, 0x67,
|
||||
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x12, 0x47, 0x0a, 0x15, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x54, 0x4c,
|
||||
0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x67,
|
||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45,
|
||||
0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x36, 0x5a, 0x34,
|
||||
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x6e, 0x4d, 0x61, 0x69, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x2d, 0x62, 0x72, 0x69,
|
||||
0x64, 0x67, 0x65, 0x2f, 0x76, 0x33, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f,
|
||||
0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4d, 0x0a, 0x15,
|
||||
0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
|
||||
0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x0e, 0x52,
|
||||
0x75, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x18, 0x2e,
|
||||
0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53,
|
||||
0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x0f,
|
||||
0x53, 0x74, 0x6f, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12,
|
||||
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12,
|
||||
0x3f, 0x0a, 0x0d, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x70, 0x61, 0x69, 0x72,
|
||||
0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
||||
0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x4d, 0x61, 0x69, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6e,
|
||||
0x2d, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x2f, 0x76, 0x33, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@ -5938,81 +5924,75 @@ var file_bridge_proto_depIdxs = []int32{
|
||||
80, // 121: grpc.Bridge.LogoutUser:input_type -> google.protobuf.StringValue
|
||||
80, // 122: grpc.Bridge.RemoveUser:input_type -> google.protobuf.StringValue
|
||||
18, // 123: grpc.Bridge.ConfigureUserAppleMail:input_type -> grpc.ConfigureAppleMailRequest
|
||||
81, // 124: grpc.Bridge.ReportBugClicked:input_type -> google.protobuf.Empty
|
||||
80, // 125: grpc.Bridge.AutoconfigClicked:input_type -> google.protobuf.StringValue
|
||||
80, // 126: grpc.Bridge.ExternalLinkClicked:input_type -> google.protobuf.StringValue
|
||||
81, // 127: grpc.Bridge.IsTLSCertificateInstalled:input_type -> google.protobuf.Empty
|
||||
81, // 128: grpc.Bridge.InstallTLSCertificate:input_type -> google.protobuf.Empty
|
||||
80, // 129: grpc.Bridge.ExportTLSCertificates:input_type -> google.protobuf.StringValue
|
||||
19, // 130: grpc.Bridge.RunEventStream:input_type -> grpc.EventStreamRequest
|
||||
81, // 131: grpc.Bridge.StopEventStream:input_type -> google.protobuf.Empty
|
||||
81, // 132: grpc.Bridge.TriggerRepair:input_type -> google.protobuf.Empty
|
||||
80, // 133: grpc.Bridge.CheckTokens:output_type -> google.protobuf.StringValue
|
||||
81, // 134: grpc.Bridge.AddLogEntry:output_type -> google.protobuf.Empty
|
||||
8, // 135: grpc.Bridge.GuiReady:output_type -> grpc.GuiReadyResponse
|
||||
81, // 136: grpc.Bridge.Quit:output_type -> google.protobuf.Empty
|
||||
81, // 137: grpc.Bridge.Restart:output_type -> google.protobuf.Empty
|
||||
82, // 138: grpc.Bridge.ShowOnStartup:output_type -> google.protobuf.BoolValue
|
||||
81, // 139: grpc.Bridge.SetIsAutostartOn:output_type -> google.protobuf.Empty
|
||||
82, // 140: grpc.Bridge.IsAutostartOn:output_type -> google.protobuf.BoolValue
|
||||
81, // 141: grpc.Bridge.SetIsBetaEnabled:output_type -> google.protobuf.Empty
|
||||
82, // 142: grpc.Bridge.IsBetaEnabled:output_type -> google.protobuf.BoolValue
|
||||
81, // 143: grpc.Bridge.SetIsAllMailVisible:output_type -> google.protobuf.Empty
|
||||
82, // 144: grpc.Bridge.IsAllMailVisible:output_type -> google.protobuf.BoolValue
|
||||
81, // 145: grpc.Bridge.SetIsTelemetryDisabled:output_type -> google.protobuf.Empty
|
||||
82, // 146: grpc.Bridge.IsTelemetryDisabled:output_type -> google.protobuf.BoolValue
|
||||
80, // 147: grpc.Bridge.GoOs:output_type -> google.protobuf.StringValue
|
||||
81, // 148: grpc.Bridge.TriggerReset:output_type -> google.protobuf.Empty
|
||||
80, // 149: grpc.Bridge.Version:output_type -> google.protobuf.StringValue
|
||||
80, // 150: grpc.Bridge.LogsPath:output_type -> google.protobuf.StringValue
|
||||
80, // 151: grpc.Bridge.LicensePath:output_type -> google.protobuf.StringValue
|
||||
80, // 152: grpc.Bridge.ReleaseNotesPageLink:output_type -> google.protobuf.StringValue
|
||||
80, // 153: grpc.Bridge.DependencyLicensesLink:output_type -> google.protobuf.StringValue
|
||||
80, // 154: grpc.Bridge.LandingPageLink:output_type -> google.protobuf.StringValue
|
||||
81, // 155: grpc.Bridge.SetColorSchemeName:output_type -> google.protobuf.Empty
|
||||
80, // 156: grpc.Bridge.ColorSchemeName:output_type -> google.protobuf.StringValue
|
||||
80, // 157: grpc.Bridge.CurrentEmailClient:output_type -> google.protobuf.StringValue
|
||||
81, // 158: grpc.Bridge.ReportBug:output_type -> google.protobuf.Empty
|
||||
81, // 159: grpc.Bridge.ForceLauncher:output_type -> google.protobuf.Empty
|
||||
81, // 160: grpc.Bridge.SetMainExecutable:output_type -> google.protobuf.Empty
|
||||
81, // 161: grpc.Bridge.RequestKnowledgeBaseSuggestions:output_type -> google.protobuf.Empty
|
||||
81, // 162: grpc.Bridge.Login:output_type -> google.protobuf.Empty
|
||||
81, // 163: grpc.Bridge.Login2FA:output_type -> google.protobuf.Empty
|
||||
81, // 164: grpc.Bridge.Login2Passwords:output_type -> google.protobuf.Empty
|
||||
81, // 165: grpc.Bridge.LoginAbort:output_type -> google.protobuf.Empty
|
||||
81, // 166: grpc.Bridge.CheckUpdate:output_type -> google.protobuf.Empty
|
||||
81, // 167: grpc.Bridge.InstallUpdate:output_type -> google.protobuf.Empty
|
||||
81, // 168: grpc.Bridge.SetIsAutomaticUpdateOn:output_type -> google.protobuf.Empty
|
||||
82, // 169: grpc.Bridge.IsAutomaticUpdateOn:output_type -> google.protobuf.BoolValue
|
||||
80, // 170: grpc.Bridge.DiskCachePath:output_type -> google.protobuf.StringValue
|
||||
81, // 171: grpc.Bridge.SetDiskCachePath:output_type -> google.protobuf.Empty
|
||||
81, // 172: grpc.Bridge.SetIsDoHEnabled:output_type -> google.protobuf.Empty
|
||||
82, // 173: grpc.Bridge.IsDoHEnabled:output_type -> google.protobuf.BoolValue
|
||||
12, // 174: grpc.Bridge.MailServerSettings:output_type -> grpc.ImapSmtpSettings
|
||||
81, // 175: grpc.Bridge.SetMailServerSettings:output_type -> google.protobuf.Empty
|
||||
80, // 176: grpc.Bridge.Hostname:output_type -> google.protobuf.StringValue
|
||||
82, // 177: grpc.Bridge.IsPortFree:output_type -> google.protobuf.BoolValue
|
||||
13, // 178: grpc.Bridge.AvailableKeychains:output_type -> grpc.AvailableKeychainsResponse
|
||||
81, // 179: grpc.Bridge.SetCurrentKeychain:output_type -> google.protobuf.Empty
|
||||
80, // 180: grpc.Bridge.CurrentKeychain:output_type -> google.protobuf.StringValue
|
||||
17, // 181: grpc.Bridge.GetUserList:output_type -> grpc.UserListResponse
|
||||
14, // 182: grpc.Bridge.GetUser:output_type -> grpc.User
|
||||
81, // 183: grpc.Bridge.SetUserSplitMode:output_type -> google.protobuf.Empty
|
||||
81, // 184: grpc.Bridge.SendBadEventUserFeedback:output_type -> google.protobuf.Empty
|
||||
81, // 185: grpc.Bridge.LogoutUser:output_type -> google.protobuf.Empty
|
||||
81, // 186: grpc.Bridge.RemoveUser:output_type -> google.protobuf.Empty
|
||||
81, // 187: grpc.Bridge.ConfigureUserAppleMail:output_type -> google.protobuf.Empty
|
||||
81, // 188: grpc.Bridge.ReportBugClicked:output_type -> google.protobuf.Empty
|
||||
81, // 189: grpc.Bridge.AutoconfigClicked:output_type -> google.protobuf.Empty
|
||||
81, // 190: grpc.Bridge.ExternalLinkClicked:output_type -> google.protobuf.Empty
|
||||
82, // 191: grpc.Bridge.IsTLSCertificateInstalled:output_type -> google.protobuf.BoolValue
|
||||
81, // 192: grpc.Bridge.InstallTLSCertificate:output_type -> google.protobuf.Empty
|
||||
81, // 193: grpc.Bridge.ExportTLSCertificates:output_type -> google.protobuf.Empty
|
||||
20, // 194: grpc.Bridge.RunEventStream:output_type -> grpc.StreamEvent
|
||||
81, // 195: grpc.Bridge.StopEventStream:output_type -> google.protobuf.Empty
|
||||
81, // 196: grpc.Bridge.TriggerRepair:output_type -> google.protobuf.Empty
|
||||
133, // [133:197] is the sub-list for method output_type
|
||||
69, // [69:133] is the sub-list for method input_type
|
||||
81, // 124: grpc.Bridge.IsTLSCertificateInstalled:input_type -> google.protobuf.Empty
|
||||
81, // 125: grpc.Bridge.InstallTLSCertificate:input_type -> google.protobuf.Empty
|
||||
80, // 126: grpc.Bridge.ExportTLSCertificates:input_type -> google.protobuf.StringValue
|
||||
19, // 127: grpc.Bridge.RunEventStream:input_type -> grpc.EventStreamRequest
|
||||
81, // 128: grpc.Bridge.StopEventStream:input_type -> google.protobuf.Empty
|
||||
81, // 129: grpc.Bridge.TriggerRepair:input_type -> google.protobuf.Empty
|
||||
80, // 130: grpc.Bridge.CheckTokens:output_type -> google.protobuf.StringValue
|
||||
81, // 131: grpc.Bridge.AddLogEntry:output_type -> google.protobuf.Empty
|
||||
8, // 132: grpc.Bridge.GuiReady:output_type -> grpc.GuiReadyResponse
|
||||
81, // 133: grpc.Bridge.Quit:output_type -> google.protobuf.Empty
|
||||
81, // 134: grpc.Bridge.Restart:output_type -> google.protobuf.Empty
|
||||
82, // 135: grpc.Bridge.ShowOnStartup:output_type -> google.protobuf.BoolValue
|
||||
81, // 136: grpc.Bridge.SetIsAutostartOn:output_type -> google.protobuf.Empty
|
||||
82, // 137: grpc.Bridge.IsAutostartOn:output_type -> google.protobuf.BoolValue
|
||||
81, // 138: grpc.Bridge.SetIsBetaEnabled:output_type -> google.protobuf.Empty
|
||||
82, // 139: grpc.Bridge.IsBetaEnabled:output_type -> google.protobuf.BoolValue
|
||||
81, // 140: grpc.Bridge.SetIsAllMailVisible:output_type -> google.protobuf.Empty
|
||||
82, // 141: grpc.Bridge.IsAllMailVisible:output_type -> google.protobuf.BoolValue
|
||||
81, // 142: grpc.Bridge.SetIsTelemetryDisabled:output_type -> google.protobuf.Empty
|
||||
82, // 143: grpc.Bridge.IsTelemetryDisabled:output_type -> google.protobuf.BoolValue
|
||||
80, // 144: grpc.Bridge.GoOs:output_type -> google.protobuf.StringValue
|
||||
81, // 145: grpc.Bridge.TriggerReset:output_type -> google.protobuf.Empty
|
||||
80, // 146: grpc.Bridge.Version:output_type -> google.protobuf.StringValue
|
||||
80, // 147: grpc.Bridge.LogsPath:output_type -> google.protobuf.StringValue
|
||||
80, // 148: grpc.Bridge.LicensePath:output_type -> google.protobuf.StringValue
|
||||
80, // 149: grpc.Bridge.ReleaseNotesPageLink:output_type -> google.protobuf.StringValue
|
||||
80, // 150: grpc.Bridge.DependencyLicensesLink:output_type -> google.protobuf.StringValue
|
||||
80, // 151: grpc.Bridge.LandingPageLink:output_type -> google.protobuf.StringValue
|
||||
81, // 152: grpc.Bridge.SetColorSchemeName:output_type -> google.protobuf.Empty
|
||||
80, // 153: grpc.Bridge.ColorSchemeName:output_type -> google.protobuf.StringValue
|
||||
80, // 154: grpc.Bridge.CurrentEmailClient:output_type -> google.protobuf.StringValue
|
||||
81, // 155: grpc.Bridge.ReportBug:output_type -> google.protobuf.Empty
|
||||
81, // 156: grpc.Bridge.ForceLauncher:output_type -> google.protobuf.Empty
|
||||
81, // 157: grpc.Bridge.SetMainExecutable:output_type -> google.protobuf.Empty
|
||||
81, // 158: grpc.Bridge.RequestKnowledgeBaseSuggestions:output_type -> google.protobuf.Empty
|
||||
81, // 159: grpc.Bridge.Login:output_type -> google.protobuf.Empty
|
||||
81, // 160: grpc.Bridge.Login2FA:output_type -> google.protobuf.Empty
|
||||
81, // 161: grpc.Bridge.Login2Passwords:output_type -> google.protobuf.Empty
|
||||
81, // 162: grpc.Bridge.LoginAbort:output_type -> google.protobuf.Empty
|
||||
81, // 163: grpc.Bridge.CheckUpdate:output_type -> google.protobuf.Empty
|
||||
81, // 164: grpc.Bridge.InstallUpdate:output_type -> google.protobuf.Empty
|
||||
81, // 165: grpc.Bridge.SetIsAutomaticUpdateOn:output_type -> google.protobuf.Empty
|
||||
82, // 166: grpc.Bridge.IsAutomaticUpdateOn:output_type -> google.protobuf.BoolValue
|
||||
80, // 167: grpc.Bridge.DiskCachePath:output_type -> google.protobuf.StringValue
|
||||
81, // 168: grpc.Bridge.SetDiskCachePath:output_type -> google.protobuf.Empty
|
||||
81, // 169: grpc.Bridge.SetIsDoHEnabled:output_type -> google.protobuf.Empty
|
||||
82, // 170: grpc.Bridge.IsDoHEnabled:output_type -> google.protobuf.BoolValue
|
||||
12, // 171: grpc.Bridge.MailServerSettings:output_type -> grpc.ImapSmtpSettings
|
||||
81, // 172: grpc.Bridge.SetMailServerSettings:output_type -> google.protobuf.Empty
|
||||
80, // 173: grpc.Bridge.Hostname:output_type -> google.protobuf.StringValue
|
||||
82, // 174: grpc.Bridge.IsPortFree:output_type -> google.protobuf.BoolValue
|
||||
13, // 175: grpc.Bridge.AvailableKeychains:output_type -> grpc.AvailableKeychainsResponse
|
||||
81, // 176: grpc.Bridge.SetCurrentKeychain:output_type -> google.protobuf.Empty
|
||||
80, // 177: grpc.Bridge.CurrentKeychain:output_type -> google.protobuf.StringValue
|
||||
17, // 178: grpc.Bridge.GetUserList:output_type -> grpc.UserListResponse
|
||||
14, // 179: grpc.Bridge.GetUser:output_type -> grpc.User
|
||||
81, // 180: grpc.Bridge.SetUserSplitMode:output_type -> google.protobuf.Empty
|
||||
81, // 181: grpc.Bridge.SendBadEventUserFeedback:output_type -> google.protobuf.Empty
|
||||
81, // 182: grpc.Bridge.LogoutUser:output_type -> google.protobuf.Empty
|
||||
81, // 183: grpc.Bridge.RemoveUser:output_type -> google.protobuf.Empty
|
||||
81, // 184: grpc.Bridge.ConfigureUserAppleMail:output_type -> google.protobuf.Empty
|
||||
82, // 185: grpc.Bridge.IsTLSCertificateInstalled:output_type -> google.protobuf.BoolValue
|
||||
81, // 186: grpc.Bridge.InstallTLSCertificate:output_type -> google.protobuf.Empty
|
||||
81, // 187: grpc.Bridge.ExportTLSCertificates:output_type -> google.protobuf.Empty
|
||||
20, // 188: grpc.Bridge.RunEventStream:output_type -> grpc.StreamEvent
|
||||
81, // 189: grpc.Bridge.StopEventStream:output_type -> google.protobuf.Empty
|
||||
81, // 190: grpc.Bridge.TriggerRepair:output_type -> google.protobuf.Empty
|
||||
130, // [130:191] is the sub-list for method output_type
|
||||
69, // [69:130] is the sub-list for method input_type
|
||||
69, // [69:69] is the sub-list for extension type_name
|
||||
69, // [69:69] is the sub-list for extension extendee
|
||||
0, // [0:69] is the sub-list for field type_name
|
||||
|
||||
@ -98,11 +98,6 @@ service Bridge {
|
||||
rpc RemoveUser(google.protobuf.StringValue) returns (google.protobuf.Empty);
|
||||
rpc ConfigureUserAppleMail(ConfigureAppleMailRequest) returns (google.protobuf.Empty);
|
||||
|
||||
// Telemetry
|
||||
rpc ReportBugClicked(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||
rpc AutoconfigClicked(google.protobuf.StringValue) returns (google.protobuf.Empty);
|
||||
rpc ExternalLinkClicked(google.protobuf.StringValue) returns (google.protobuf.Empty);
|
||||
|
||||
// TLS certificate related calls
|
||||
rpc IsTLSCertificateInstalled(google.protobuf.Empty) returns (google.protobuf.BoolValue);
|
||||
rpc InstallTLSCertificate(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||
|
||||
@ -93,9 +93,6 @@ const (
|
||||
Bridge_LogoutUser_FullMethodName = "/grpc.Bridge/LogoutUser"
|
||||
Bridge_RemoveUser_FullMethodName = "/grpc.Bridge/RemoveUser"
|
||||
Bridge_ConfigureUserAppleMail_FullMethodName = "/grpc.Bridge/ConfigureUserAppleMail"
|
||||
Bridge_ReportBugClicked_FullMethodName = "/grpc.Bridge/ReportBugClicked"
|
||||
Bridge_AutoconfigClicked_FullMethodName = "/grpc.Bridge/AutoconfigClicked"
|
||||
Bridge_ExternalLinkClicked_FullMethodName = "/grpc.Bridge/ExternalLinkClicked"
|
||||
Bridge_IsTLSCertificateInstalled_FullMethodName = "/grpc.Bridge/IsTLSCertificateInstalled"
|
||||
Bridge_InstallTLSCertificate_FullMethodName = "/grpc.Bridge/InstallTLSCertificate"
|
||||
Bridge_ExportTLSCertificates_FullMethodName = "/grpc.Bridge/ExportTLSCertificates"
|
||||
@ -170,10 +167,6 @@ type BridgeClient interface {
|
||||
LogoutUser(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
RemoveUser(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
ConfigureUserAppleMail(ctx context.Context, in *ConfigureAppleMailRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
// Telemetry
|
||||
ReportBugClicked(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
AutoconfigClicked(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
ExternalLinkClicked(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
// TLS certificate related calls
|
||||
IsTLSCertificateInstalled(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error)
|
||||
InstallTLSCertificate(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
@ -688,33 +681,6 @@ func (c *bridgeClient) ConfigureUserAppleMail(ctx context.Context, in *Configure
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *bridgeClient) ReportBugClicked(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, Bridge_ReportBugClicked_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *bridgeClient) AutoconfigClicked(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, Bridge_AutoconfigClicked_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *bridgeClient) ExternalLinkClicked(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, Bridge_ExternalLinkClicked_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *bridgeClient) IsTLSCertificateInstalled(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error) {
|
||||
out := new(wrapperspb.BoolValue)
|
||||
err := c.cc.Invoke(ctx, Bridge_IsTLSCertificateInstalled_FullMethodName, in, out, opts...)
|
||||
@ -858,10 +824,6 @@ type BridgeServer interface {
|
||||
LogoutUser(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
|
||||
RemoveUser(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
|
||||
ConfigureUserAppleMail(context.Context, *ConfigureAppleMailRequest) (*emptypb.Empty, error)
|
||||
// Telemetry
|
||||
ReportBugClicked(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||
AutoconfigClicked(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
|
||||
ExternalLinkClicked(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
|
||||
// TLS certificate related calls
|
||||
IsTLSCertificateInstalled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error)
|
||||
InstallTLSCertificate(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||
@ -1043,15 +1005,6 @@ func (UnimplementedBridgeServer) RemoveUser(context.Context, *wrapperspb.StringV
|
||||
func (UnimplementedBridgeServer) ConfigureUserAppleMail(context.Context, *ConfigureAppleMailRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ConfigureUserAppleMail not implemented")
|
||||
}
|
||||
func (UnimplementedBridgeServer) ReportBugClicked(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ReportBugClicked not implemented")
|
||||
}
|
||||
func (UnimplementedBridgeServer) AutoconfigClicked(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AutoconfigClicked not implemented")
|
||||
}
|
||||
func (UnimplementedBridgeServer) ExternalLinkClicked(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ExternalLinkClicked not implemented")
|
||||
}
|
||||
func (UnimplementedBridgeServer) IsTLSCertificateInstalled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method IsTLSCertificateInstalled not implemented")
|
||||
}
|
||||
@ -2073,60 +2026,6 @@ func _Bridge_ConfigureUserAppleMail_Handler(srv interface{}, ctx context.Context
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Bridge_ReportBugClicked_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(emptypb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(BridgeServer).ReportBugClicked(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Bridge_ReportBugClicked_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BridgeServer).ReportBugClicked(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Bridge_AutoconfigClicked_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(wrapperspb.StringValue)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(BridgeServer).AutoconfigClicked(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Bridge_AutoconfigClicked_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BridgeServer).AutoconfigClicked(ctx, req.(*wrapperspb.StringValue))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Bridge_ExternalLinkClicked_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(wrapperspb.StringValue)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(BridgeServer).ExternalLinkClicked(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Bridge_ExternalLinkClicked_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BridgeServer).ExternalLinkClicked(ctx, req.(*wrapperspb.StringValue))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Bridge_IsTLSCertificateInstalled_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(emptypb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
@ -2465,18 +2364,6 @@ var Bridge_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "ConfigureUserAppleMail",
|
||||
Handler: _Bridge_ConfigureUserAppleMail_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ReportBugClicked",
|
||||
Handler: _Bridge_ReportBugClicked_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "AutoconfigClicked",
|
||||
Handler: _Bridge_AutoconfigClicked_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ExternalLinkClicked",
|
||||
Handler: _Bridge_ExternalLinkClicked_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "IsTLSCertificateInstalled",
|
||||
Handler: _Bridge_IsTLSCertificateInstalled_Handler,
|
||||
|
||||
@ -214,7 +214,10 @@ func NewUserBadEvent(userID string, errorMessage string) *StreamEvent {
|
||||
}
|
||||
|
||||
func NewUsedBytesChangedEvent(userID string, usedBytes uint64) *StreamEvent {
|
||||
return userEvent(&UserEvent{Event: &UserEvent_UsedBytesChangedEvent{UsedBytesChangedEvent: &UsedBytesChangedEvent{UserID: userID, UsedBytes: int64(usedBytes)}}})
|
||||
return userEvent(&UserEvent{Event: &UserEvent_UsedBytesChangedEvent{UsedBytesChangedEvent: &UsedBytesChangedEvent{
|
||||
UserID: userID,
|
||||
UsedBytes: int64(usedBytes), //nolint:gosec // disable G115
|
||||
}}})
|
||||
}
|
||||
|
||||
func newIMAPLoginFailedEvent(username string) *StreamEvent {
|
||||
|
||||
@ -465,7 +465,7 @@ func (s *Service) finishLogin() {
|
||||
|
||||
if apiErr := new(proton.APIError); errors.As(err, &apiErr) && apiErr.Code == proton.HumanValidationInvalidToken {
|
||||
s.hvDetails = nil
|
||||
_ = s.SendEvent(NewLoginError(LoginErrorType_HV_ERROR, err.Error()))
|
||||
_ = s.SendEvent(NewLoginError(LoginErrorType_HV_ERROR, hv.VerificationFailedErrorMsg))
|
||||
return
|
||||
}
|
||||
|
||||
@ -643,7 +643,10 @@ func (s *Service) monitorParentPID() {
|
||||
func (s *Service) handleHvRequest(err error) {
|
||||
hvDet, hvErr := hv.VerifyAndExtractHvRequest(err)
|
||||
if hvErr != nil {
|
||||
_ = s.SendEvent(NewLoginError(LoginErrorType_HV_ERROR, hvErr.Error()))
|
||||
_ = s.SendEvent(NewLoginError(LoginErrorType_HV_ERROR, hv.ExtractionErrorMsg))
|
||||
s.bridge.ReportMessageWithContext("Unable to extract HV request details", map[string]any{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -30,6 +30,7 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/frontend/theme"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/hv"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/kb"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/service"
|
||||
@ -468,7 +469,7 @@ func (s *Service) Login(_ context.Context, login *LoginRequest) (*emptypb.Empty,
|
||||
|
||||
case proton.HumanValidationInvalidToken:
|
||||
s.hvDetails = nil
|
||||
_ = s.SendEvent(NewLoginError(LoginErrorType_HV_ERROR, err.Error()))
|
||||
_ = s.SendEvent(NewLoginError(LoginErrorType_HV_ERROR, hv.VerificationFailedErrorMsg))
|
||||
|
||||
default:
|
||||
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, err.Error()))
|
||||
@ -717,8 +718,8 @@ func (s *Service) MailServerSettings(_ context.Context, _ *emptypb.Empty) (*Imap
|
||||
state: protoimpl.MessageState{},
|
||||
sizeCache: 0,
|
||||
unknownFields: nil,
|
||||
ImapPort: int32(s.bridge.GetIMAPPort()),
|
||||
SmtpPort: int32(s.bridge.GetSMTPPort()),
|
||||
ImapPort: int32(s.bridge.GetIMAPPort()), //nolint:gosec // disable G115
|
||||
SmtpPort: int32(s.bridge.GetSMTPPort()), //nolint:gosec // disable G115
|
||||
UseSSLForImap: s.bridge.GetIMAPSSL(),
|
||||
UseSSLForSmtp: s.bridge.GetSMTPSSL(),
|
||||
}, nil
|
||||
@ -864,8 +865,8 @@ func base64Decode(in []byte) ([]byte, error) {
|
||||
|
||||
func (s *Service) getMailServerSettings() *ImapSmtpSettings {
|
||||
return &ImapSmtpSettings{
|
||||
ImapPort: int32(s.bridge.GetIMAPPort()),
|
||||
SmtpPort: int32(s.bridge.GetSMTPPort()),
|
||||
ImapPort: int32(s.bridge.GetIMAPPort()), //nolint:gosec // disable G115
|
||||
SmtpPort: int32(s.bridge.GetSMTPPort()), //nolint:gosec // disable G115
|
||||
UseSSLForImap: s.bridge.GetIMAPSSL(),
|
||||
UseSSLForSmtp: s.bridge.GetSMTPSSL(),
|
||||
}
|
||||
|
||||
@ -1,44 +0,0 @@
|
||||
// Copyright (c) 2024 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ProtonMail/gluon/async"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
)
|
||||
|
||||
func (s *Service) ReportBugClicked(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
defer async.HandlePanic(s.panicHandler)
|
||||
s.bridge.ReportBugClicked()
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *Service) AutoconfigClicked(_ context.Context, client *wrapperspb.StringValue) (*emptypb.Empty, error) {
|
||||
defer async.HandlePanic(s.panicHandler)
|
||||
s.bridge.AutoconfigUsed(client.Value)
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *Service) ExternalLinkClicked(_ context.Context, article *wrapperspb.StringValue) (*emptypb.Empty, error) {
|
||||
defer async.HandlePanic(s.panicHandler)
|
||||
s.bridge.ExternalLinkClicked(article.Value)
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
@ -71,8 +71,8 @@ func grpcUserFromInfo(user bridge.UserInfo) *User {
|
||||
AvatarText: getInitials(user.Username),
|
||||
State: userStateToGrpc(user.State),
|
||||
SplitMode: user.AddressMode == vault.SplitMode,
|
||||
UsedBytes: int64(user.UsedSpace),
|
||||
TotalBytes: int64(user.MaxSpace),
|
||||
UsedBytes: int64(user.UsedSpace), //nolint:gosec // disable G115
|
||||
TotalBytes: int64(user.MaxSpace), //nolint:gosec // disable G115
|
||||
Password: user.BridgePass,
|
||||
Addresses: user.Addresses,
|
||||
}
|
||||
|
||||
@ -21,6 +21,11 @@ import (
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
)
|
||||
|
||||
const (
|
||||
ExtractionErrorMsg = "Human verification requested, but an issue occurred. Please try again."
|
||||
VerificationFailedErrorMsg = "Human verification failed. Please try again."
|
||||
)
|
||||
|
||||
// VerifyAndExtractHvRequest expects an error request as input
|
||||
// determines whether the given error is a Proton human verification request; if it isn't then it returns -> nil, nil (no details, no error)
|
||||
// if it is a HV req. then it tries to parse the json data and verify that the captcha method is included; if either fails -> nil, err
|
||||
@ -34,7 +39,7 @@ func VerifyAndExtractHvRequest(err error) (*proton.APIHVDetails, error) {
|
||||
if errors.As(err, &protonErr) && protonErr.IsHVError() {
|
||||
hvDetails, hvErr := protonErr.GetHVDetails()
|
||||
if hvErr != nil {
|
||||
return nil, fmt.Errorf("received HV request, but can't decode HV details")
|
||||
return nil, hvErr
|
||||
}
|
||||
return hvDetails, nil
|
||||
}
|
||||
|
||||
@ -99,7 +99,7 @@ func GetArticleIndex(url string) (uint64, error) {
|
||||
if index == -1 {
|
||||
return 0, ErrArticleNotFound
|
||||
}
|
||||
return uint64(index), nil
|
||||
return uint64(index), nil //nolint:gosec // disable G115
|
||||
}
|
||||
|
||||
func simplifyUserInput(input string) string {
|
||||
|
||||
@ -188,16 +188,6 @@ func (l *Locations) ProvideUpdatesPath() (string, error) {
|
||||
return l.getUpdatesPath(), nil
|
||||
}
|
||||
|
||||
// ProvideStatsPath returns a location for statistics files (e.g. ~/.local/share/<company>/<app>/stats).
|
||||
// It creates it if it doesn't already exist.
|
||||
func (l *Locations) ProvideStatsPath() (string, error) {
|
||||
if err := os.MkdirAll(l.getStatsPath(), 0o700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return l.getStatsPath(), nil
|
||||
}
|
||||
|
||||
func (l *Locations) ProvideIMAPSyncConfigPath() (string, error) {
|
||||
if err := os.MkdirAll(l.getIMAPSyncConfigPath(), 0o700); err != nil {
|
||||
return "", err
|
||||
@ -252,10 +242,6 @@ func (l *Locations) getNotificationsCachePath() string {
|
||||
return filepath.Join(l.userCache, "notifications")
|
||||
}
|
||||
|
||||
func (l *Locations) getStatsPath() string {
|
||||
return filepath.Join(l.userData, "stats")
|
||||
}
|
||||
|
||||
func (l *Locations) getUnleashCachePath() string { return filepath.Join(l.userCache, "unleash_cache") }
|
||||
|
||||
// Clear removes everything except the lock and update files.
|
||||
|
||||
@ -31,8 +31,8 @@ type CoolDownProvider interface {
|
||||
Reset()
|
||||
}
|
||||
|
||||
func jitter(max int) time.Duration {
|
||||
return time.Duration(rand.Intn(max)) * time.Second //nolint:gosec
|
||||
func jitter(maxValue int) time.Duration {
|
||||
return time.Duration(rand.Intn(maxValue)) * time.Second //nolint:gosec
|
||||
}
|
||||
|
||||
type ExpCoolDown struct {
|
||||
|
||||
87
internal/plan/plan.go
Normal file
87
internal/plan/plan.go
Normal file
@ -0,0 +1,87 @@
|
||||
// Copyright (c) 2024 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package plan
|
||||
|
||||
import "strings"
|
||||
|
||||
const (
|
||||
Unknown = "unknown"
|
||||
Other = "other"
|
||||
Business = "business"
|
||||
Individual = "individual"
|
||||
Group = "group"
|
||||
)
|
||||
|
||||
var planHierarchy = map[string]int{ //nolint:gochecknoglobals
|
||||
Business: 4,
|
||||
Group: 3,
|
||||
Individual: 2,
|
||||
Other: 1,
|
||||
Unknown: 0,
|
||||
}
|
||||
|
||||
func IsHigherPriority(currentPlan, newPlan string) bool {
|
||||
newRank, ok := planHierarchy[newPlan]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
currentRank, ok2 := planHierarchy[currentPlan]
|
||||
if !ok2 {
|
||||
return true // we don't have a valid plan, might as well replace it
|
||||
}
|
||||
|
||||
return newRank > currentRank
|
||||
}
|
||||
|
||||
func MapUserPlan(planName string) string {
|
||||
if planName == "" {
|
||||
return Unknown
|
||||
}
|
||||
switch strings.TrimSpace(strings.ToLower(planName)) {
|
||||
case Individual:
|
||||
return Individual
|
||||
case Unknown:
|
||||
return Unknown
|
||||
case Business:
|
||||
return Business
|
||||
case Group:
|
||||
return Group
|
||||
case "mail2022":
|
||||
return Individual
|
||||
case "bundle2022":
|
||||
return Individual
|
||||
case "family2022":
|
||||
return Group
|
||||
case "visionary2022":
|
||||
return Group
|
||||
case "mailpro2022":
|
||||
return Business
|
||||
case "planbiz2024":
|
||||
return Business
|
||||
case "bundlepro2022":
|
||||
return Business
|
||||
case "bundlepro2024":
|
||||
return Business
|
||||
case "duo2024":
|
||||
return Group
|
||||
|
||||
default:
|
||||
return Other
|
||||
}
|
||||
}
|
||||
@ -21,18 +21,13 @@
|
||||
package sentry
|
||||
|
||||
import (
|
||||
"github.com/elastic/go-sysinfo"
|
||||
"github.com/elastic/go-sysinfo/types"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const translatedProcDarwin = "sysctl.proc_translated"
|
||||
|
||||
func getHostArch() string {
|
||||
host, err := sysinfo.Host()
|
||||
if err != nil {
|
||||
return "not-detected"
|
||||
}
|
||||
|
||||
func getHostArch(host types.Host) string {
|
||||
// It is not possible to retrieve real hardware architecture once using
|
||||
// rosetta. But it is possible to detect the process translation if
|
||||
// rosetta is used.
|
||||
|
||||
@ -20,12 +20,10 @@
|
||||
|
||||
package sentry
|
||||
|
||||
import "github.com/elastic/go-sysinfo"
|
||||
import (
|
||||
"github.com/elastic/go-sysinfo/types"
|
||||
)
|
||||
|
||||
func getHostArch() string {
|
||||
host, err := sysinfo.Host()
|
||||
if err != nil {
|
||||
return "not-detected"
|
||||
}
|
||||
func getHostArch(host types.Host) string {
|
||||
return host.Info().Architecture
|
||||
}
|
||||
|
||||
@ -30,10 +30,13 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/v3/pkg/algo"
|
||||
"github.com/ProtonMail/proton-bridge/v3/pkg/restarter"
|
||||
"github.com/elastic/go-sysinfo"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const hostNotDetectedField = "not-detected"
|
||||
|
||||
var skippedFunctions = []string{} //nolint:gochecknoglobals
|
||||
|
||||
func init() { //nolint:gochecknoinits
|
||||
@ -70,17 +73,50 @@ func init() { //nolint:gochecknoinits
|
||||
)
|
||||
}
|
||||
|
||||
type hostInfoData struct {
|
||||
hostArch string
|
||||
hostName string
|
||||
hostVersion string
|
||||
hostBuild string
|
||||
}
|
||||
|
||||
func newHostInfoData() hostInfoData {
|
||||
return hostInfoData{
|
||||
hostArch: hostNotDetectedField,
|
||||
hostName: hostNotDetectedField,
|
||||
hostVersion: hostNotDetectedField,
|
||||
hostBuild: hostNotDetectedField,
|
||||
}
|
||||
}
|
||||
|
||||
type Reporter struct {
|
||||
appName string
|
||||
appVersion string
|
||||
identifier Identifier
|
||||
hostArch string
|
||||
hostInfo hostInfoData
|
||||
}
|
||||
|
||||
type Identifier interface {
|
||||
GetUserAgent() string
|
||||
}
|
||||
|
||||
func getHostInfo() hostInfoData {
|
||||
data := newHostInfoData()
|
||||
|
||||
host, err := sysinfo.Host()
|
||||
if err != nil {
|
||||
return data
|
||||
}
|
||||
|
||||
data.hostArch = getHostArch(host)
|
||||
osInfo := host.Info().OS
|
||||
data.hostName = osInfo.Name
|
||||
data.hostVersion = osInfo.Version
|
||||
data.hostBuild = osInfo.Build
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func GetProtectedHostname() string {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
@ -100,7 +136,7 @@ func NewReporter(appName string, identifier Identifier) *Reporter {
|
||||
appName: appName,
|
||||
appVersion: constants.Revision,
|
||||
identifier: identifier,
|
||||
hostArch: getHostArch(),
|
||||
hostInfo: getHostInfo(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,11 +188,14 @@ func (r *Reporter) scopedReport(context map[string]interface{}, doReport func())
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"OS": runtime.GOOS,
|
||||
"Client": r.appName,
|
||||
"Version": r.appVersion,
|
||||
"UserAgent": r.identifier.GetUserAgent(),
|
||||
"HostArch": r.hostArch,
|
||||
"OS": runtime.GOOS,
|
||||
"Client": r.appName,
|
||||
"Version": r.appVersion,
|
||||
"UserAgent": r.identifier.GetUserAgent(),
|
||||
"HostArch": r.hostInfo.hostArch,
|
||||
"HostName": r.hostInfo.hostName,
|
||||
"HostVersion": r.hostInfo.hostVersion,
|
||||
"HostBuild": r.hostInfo.hostBuild,
|
||||
}
|
||||
|
||||
sentry.WithScope(func(scope *sentry.Scope) {
|
||||
|
||||
@ -31,6 +31,7 @@ import (
|
||||
"github.com/ProtonMail/gluon/connector"
|
||||
"github.com/ProtonMail/gluon/imap"
|
||||
"github.com/ProtonMail/gluon/reporter"
|
||||
"github.com/ProtonMail/gluon/rfc5322"
|
||||
"github.com/ProtonMail/gluon/rfc822"
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
@ -55,7 +56,6 @@ type Connector struct {
|
||||
|
||||
identityState sharedIdentity
|
||||
client APIClient
|
||||
telemetry Telemetry
|
||||
reporter reporter.Reporter
|
||||
panicHandler async.PanicHandler
|
||||
sendRecorder *sendrecorder.SendRecorder
|
||||
@ -69,6 +69,8 @@ type Connector struct {
|
||||
syncState *SyncState
|
||||
}
|
||||
|
||||
var errNoSenderAddressMatch = errors.New("no matching sender found in address list")
|
||||
|
||||
func NewConnector(
|
||||
addrID string,
|
||||
apiClient APIClient,
|
||||
@ -77,7 +79,6 @@ func NewConnector(
|
||||
addressMode usertypes.AddressMode,
|
||||
sendRecorder *sendrecorder.SendRecorder,
|
||||
panicHandler async.PanicHandler,
|
||||
telemetry Telemetry,
|
||||
reporter reporter.Reporter,
|
||||
showAllMail bool,
|
||||
syncState *SyncState,
|
||||
@ -93,7 +94,6 @@ func NewConnector(
|
||||
attrs: defaultMailboxAttributes(),
|
||||
|
||||
client: apiClient,
|
||||
telemetry: telemetry,
|
||||
reporter: reporter,
|
||||
panicHandler: panicHandler,
|
||||
sendRecorder: sendRecorder,
|
||||
@ -107,6 +107,7 @@ func NewConnector(
|
||||
labels: labels,
|
||||
addressMode: addressMode,
|
||||
log: logrus.WithFields(logrus.Fields{
|
||||
"pkg": "imapservice",
|
||||
"gluon-connector": addressMode,
|
||||
"addr-id": addrID,
|
||||
"user-id": userID,
|
||||
@ -165,10 +166,9 @@ func (s *Connector) Init(ctx context.Context, cache connector.IMAPState) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Connector) Authorize(ctx context.Context, username string, password []byte) bool {
|
||||
func (s *Connector) Authorize(_ context.Context, username string, password []byte) bool {
|
||||
addrID, err := s.identityState.CheckAuth(username, password)
|
||||
if err != nil {
|
||||
s.telemetry.ReportConfigStatusFailure("IMAP " + err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
@ -176,8 +176,6 @@ func (s *Connector) Authorize(ctx context.Context, username string, password []b
|
||||
return false
|
||||
}
|
||||
|
||||
s.telemetry.SendConfigStatusSuccess(ctx)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@ -676,20 +674,18 @@ func (s *Connector) importMessage(
|
||||
) (imap.Message, []byte, error) {
|
||||
var full proton.FullMessage
|
||||
|
||||
// addr is primary for combined mode or active for split mode
|
||||
addr, ok := s.identityState.GetAddress(s.addrID)
|
||||
if !ok {
|
||||
return imap.Message{}, nil, fmt.Errorf("could not find address")
|
||||
}
|
||||
|
||||
p, err2 := parser.New(bytes.NewReader(literal))
|
||||
if err2 != nil {
|
||||
return imap.Message{}, nil, fmt.Errorf("failed to parse literal: %w", err2)
|
||||
}
|
||||
|
||||
isDraft := slices.Contains(labelIDs, proton.DraftsLabel)
|
||||
addr, err := s.getImportAddress(p, isDraft)
|
||||
if err != nil {
|
||||
return imap.Message{}, nil, err
|
||||
}
|
||||
|
||||
if err := s.identityState.WithAddrKR(s.addrID, func(_, addrKR *crypto.KeyRing) error {
|
||||
if err := s.identityState.WithAddrKR(addr.ID, func(_, addrKR *crypto.KeyRing) error {
|
||||
primaryKey, errKey := addrKR.FirstKey()
|
||||
if errKey != nil {
|
||||
return fmt.Errorf("failed to get primary key for import: %w", errKey)
|
||||
@ -716,7 +712,7 @@ func (s *Connector) importMessage(
|
||||
}
|
||||
str, err := s.client.ImportMessages(ctx, primaryKey, 1, 1, []proton.ImportReq{{
|
||||
Metadata: proton.ImportMetadata{
|
||||
AddressID: s.addrID,
|
||||
AddressID: addr.ID,
|
||||
LabelIDs: labelIDs,
|
||||
Unread: proton.Bool(unread),
|
||||
Flags: flags,
|
||||
@ -874,3 +870,75 @@ func stripPlusAlias(a string) string {
|
||||
func equalAddresses(a, b string) bool {
|
||||
return strings.EqualFold(stripPlusAlias(a), stripPlusAlias(b))
|
||||
}
|
||||
|
||||
func (s *Connector) getImportAddress(p *parser.Parser, isDraft bool) (proton.Address, error) {
|
||||
// addr is primary for combined mode or active for split mode
|
||||
address, ok := s.identityState.GetAddress(s.addrID)
|
||||
if !ok {
|
||||
return proton.Address{}, errors.New("could not find account address")
|
||||
}
|
||||
|
||||
inCombinedMode := s.addressMode == usertypes.AddressModeCombined
|
||||
if !inCombinedMode {
|
||||
return address, nil
|
||||
}
|
||||
|
||||
senderAddr, err := s.getSenderProtonAddress(p)
|
||||
if err != nil {
|
||||
if !errors.Is(err, errNoSenderAddressMatch) {
|
||||
s.log.WithError(err).Warn("Could not get import address")
|
||||
}
|
||||
|
||||
// We did not find a match, so we use the default address.
|
||||
return address, nil
|
||||
}
|
||||
|
||||
if senderAddr.ID == address.ID {
|
||||
return address, nil
|
||||
}
|
||||
|
||||
// GODT-3185 / BRIDGE-120 In combined mode, in certain cases we adapt the address used for encryption.
|
||||
// - draft with non-default address in combined mode: using sender address
|
||||
// - import with non-default address in combined mode: using sender address
|
||||
// - import with non-default disabled address in combined mode: using sender address
|
||||
|
||||
isSenderAddressDisabled := (!bool(senderAddr.Send)) || (senderAddr.Status != proton.AddressStatusEnabled)
|
||||
if isDraft && isSenderAddressDisabled {
|
||||
return address, nil
|
||||
}
|
||||
|
||||
return senderAddr, nil
|
||||
}
|
||||
|
||||
func (s *Connector) getSenderProtonAddress(p *parser.Parser) (proton.Address, error) {
|
||||
// Step 1: extract sender email address from message
|
||||
if (p == nil) || (p.Root() == nil) || (p.Root().Header.Len() == 0) {
|
||||
return proton.Address{}, errors.New("invalid message encountered while trying to extract sender address")
|
||||
}
|
||||
|
||||
addrField := p.Root().Header.Get("From")
|
||||
if len(addrField) == 0 {
|
||||
addrField = p.Root().Header.Get("Sender")
|
||||
}
|
||||
if len(addrField) == 0 {
|
||||
return proton.Address{}, errors.New("no sender found in message headers")
|
||||
}
|
||||
|
||||
sender, err := rfc5322.ParseAddressList(addrField)
|
||||
if (err != nil) || (len(sender) == 0) {
|
||||
return proton.Address{}, fmt.Errorf("invalid sender address in message: %w", err)
|
||||
}
|
||||
|
||||
addrStr := sender[0].Address
|
||||
|
||||
// Step 2: match email with the user address list.
|
||||
addressList := s.identityState.GetAddresses()
|
||||
index := slices.IndexFunc(addressList, func(a proton.Address) bool {
|
||||
return equalAddresses(a.Email, addrStr)
|
||||
})
|
||||
if index < 0 {
|
||||
return proton.Address{}, errNoSenderAddressMatch
|
||||
}
|
||||
|
||||
return addressList[index], nil
|
||||
}
|
||||
|
||||
@ -47,12 +47,6 @@ type EventProvider interface {
|
||||
RewindEventID(ctx context.Context, eventID string) error
|
||||
}
|
||||
|
||||
type Telemetry interface {
|
||||
useridentity.Telemetry
|
||||
SendConfigStatusSuccess(ctx context.Context)
|
||||
ReportConfigStatusFailure(errDetails string)
|
||||
}
|
||||
|
||||
type GluonIDProvider interface {
|
||||
GetGluonID(addrID string) (string, bool)
|
||||
GetGluonIDs() map[string]string
|
||||
@ -77,7 +71,6 @@ type Service struct {
|
||||
serverManager IMAPServerManager
|
||||
eventPublisher events.EventPublisher
|
||||
|
||||
telemetry Telemetry
|
||||
panicHandler async.PanicHandler
|
||||
sendRecorder *sendrecorder.SendRecorder
|
||||
reporter reporter.Reporter
|
||||
@ -112,7 +105,6 @@ func NewService(
|
||||
keyPassProvider useridentity.KeyPassProvider,
|
||||
panicHandler async.PanicHandler,
|
||||
sendRecorder *sendrecorder.SendRecorder,
|
||||
telemetry Telemetry,
|
||||
reporter reporter.Reporter,
|
||||
addressMode usertypes.AddressMode,
|
||||
subscription events.Subscription,
|
||||
@ -150,7 +142,6 @@ func NewService(
|
||||
|
||||
panicHandler: panicHandler,
|
||||
sendRecorder: sendRecorder,
|
||||
telemetry: telemetry,
|
||||
reporter: reporter,
|
||||
|
||||
connectors: make(map[string]*Connector),
|
||||
@ -242,6 +233,12 @@ func (s *Service) OnLogout(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Service) OnDelete(ctx context.Context) error {
|
||||
_, err := s.cpc.Send(ctx, &onDeleteReq{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Service) ShowAllMail(ctx context.Context, v bool) error {
|
||||
_, err := s.cpc.Send(ctx, &showAllMailReq{v: v})
|
||||
|
||||
@ -371,6 +368,11 @@ func (s *Service) run(ctx context.Context) { //nolint gocyclo
|
||||
err := s.removeConnectorsFromServer(ctx, s.connectors, false)
|
||||
req.Reply(ctx, nil, err)
|
||||
|
||||
case *onDeleteReq:
|
||||
s.log.Debug("Delete Request")
|
||||
err := s.removeConnectorsFromServer(ctx, s.connectors, true)
|
||||
req.Reply(ctx, nil, err)
|
||||
|
||||
case *showAllMailReq:
|
||||
s.log.Debug("Show all mail request")
|
||||
req.Reply(ctx, nil, nil)
|
||||
@ -513,7 +515,6 @@ func (s *Service) buildConnectors() (map[string]*Connector, error) {
|
||||
s.addressMode,
|
||||
s.sendRecorder,
|
||||
s.panicHandler,
|
||||
s.telemetry,
|
||||
s.reporter,
|
||||
s.showAllMail,
|
||||
s.syncStateProvider,
|
||||
@ -531,7 +532,6 @@ func (s *Service) buildConnectors() (map[string]*Connector, error) {
|
||||
s.addressMode,
|
||||
s.sendRecorder,
|
||||
s.panicHandler,
|
||||
s.telemetry,
|
||||
s.reporter,
|
||||
s.showAllMail,
|
||||
s.syncStateProvider,
|
||||
@ -655,6 +655,8 @@ type onLogoutReq struct{}
|
||||
|
||||
type showAllMailReq struct{ v bool }
|
||||
|
||||
type onDeleteReq struct{}
|
||||
|
||||
type setAddressModeReq struct {
|
||||
mode usertypes.AddressMode
|
||||
}
|
||||
|
||||
@ -154,7 +154,6 @@ func addNewAddressSplitMode(ctx context.Context, s *Service, addrID string) erro
|
||||
s.addressMode,
|
||||
s.sendRecorder,
|
||||
s.panicHandler,
|
||||
s.telemetry,
|
||||
s.reporter,
|
||||
s.showAllMail,
|
||||
s.syncStateProvider,
|
||||
|
||||
@ -65,6 +65,22 @@ func (s syncMessageEventHandler) HandleMessageEvents(ctx context.Context, events
|
||||
return err
|
||||
}
|
||||
|
||||
case proton.EventUpdate:
|
||||
if event.Message.IsDraft() || (event.Message.Flags&proton.MessageFlagSent != 0) {
|
||||
updates, err := onMessageUpdateDraftOrSent(
|
||||
logging.WithLogrusField(ctx, "action", "update draft or sent message (sync)"),
|
||||
s.service,
|
||||
event,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to handle update draft event (sync): %w", err)
|
||||
}
|
||||
|
||||
if err := waitOnIMAPUpdates(ctx, updates); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case proton.EventDelete:
|
||||
updates := onMessageDeleted(
|
||||
logging.WithLogrusField(ctx, "action", "delete message (sync)"),
|
||||
|
||||
@ -36,6 +36,7 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/files"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@ -48,6 +49,7 @@ type IMAPSettingsProvider interface {
|
||||
Port() int
|
||||
SetPort(int) error
|
||||
UseSSL() bool
|
||||
DisableIMAPAuthenticate() bool
|
||||
CacheDirectory() string
|
||||
DataDirectory() (string, error)
|
||||
SetCacheDirectory(string) error
|
||||
@ -73,10 +75,12 @@ func newIMAPServer(
|
||||
tlsConfig *tls.Config,
|
||||
reporter reporter.Reporter,
|
||||
logClient, logServer bool,
|
||||
disableIMAPAuthenticate bool,
|
||||
eventPublisher IMAPEventPublisher,
|
||||
tasks *async.Group,
|
||||
uidValidityGenerator imap.UIDValidityGenerator,
|
||||
panicHandler async.PanicHandler,
|
||||
observabilitySender observability.Sender,
|
||||
) (*gluon.Server, error) {
|
||||
gluonCacheDir = ApplyGluonCachePathSuffix(gluonCacheDir)
|
||||
gluonConfigDir = ApplyGluonConfigPathSuffix(gluonConfigDir)
|
||||
@ -111,7 +115,7 @@ func newIMAPServer(
|
||||
imapServerLog = io.Discard
|
||||
}
|
||||
|
||||
imapServer, err := gluon.New(
|
||||
options := []gluon.Option{
|
||||
gluon.WithTLS(tlsConfig),
|
||||
gluon.WithDataDir(gluonCacheDir),
|
||||
gluon.WithDatabaseDir(gluonConfigDir),
|
||||
@ -121,7 +125,14 @@ func newIMAPServer(
|
||||
gluon.WithReporter(reporter),
|
||||
gluon.WithUIDValidityGenerator(uidValidityGenerator),
|
||||
gluon.WithPanicHandler(panicHandler),
|
||||
)
|
||||
gluon.WithObservabilitySender(observability.NewAdapter(observabilitySender), int(observability.GluonImapError), int(observability.GluonMessageError), int(observability.GluonOtherError)),
|
||||
}
|
||||
|
||||
if disableIMAPAuthenticate {
|
||||
options = append(options, gluon.WithDisableIMAPAuthenticate())
|
||||
}
|
||||
|
||||
imapServer, err := gluon.New(options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -153,9 +164,9 @@ func newIMAPServer(
|
||||
|
||||
func getGluonVersionInfo(version *semver.Version) gluon.Option {
|
||||
return gluon.WithVersionInfo(
|
||||
int(version.Major()),
|
||||
int(version.Minor()),
|
||||
int(version.Patch()),
|
||||
int(version.Major()), //nolint:gosec // disable G115
|
||||
int(version.Minor()), //nolint:gosec // disable G115
|
||||
int(version.Patch()), //nolint:gosec // disable G115
|
||||
constants.FullAppName,
|
||||
"TODO",
|
||||
"TODO",
|
||||
|
||||
@ -31,6 +31,7 @@ import (
|
||||
"github.com/ProtonMail/gluon/reporter"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
||||
bridgesmtp "github.com/ProtonMail/proton-bridge/v3/internal/services/smtp"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/syncservice"
|
||||
"github.com/ProtonMail/proton-bridge/v3/pkg/cpc"
|
||||
@ -60,6 +61,8 @@ type Service struct {
|
||||
|
||||
uidValidityGenerator imap.UIDValidityGenerator
|
||||
telemetry Telemetry
|
||||
|
||||
observabilitySender observability.Sender
|
||||
}
|
||||
|
||||
func NewService(
|
||||
@ -71,6 +74,7 @@ func NewService(
|
||||
reporter reporter.Reporter,
|
||||
uidValidityGenerator imap.UIDValidityGenerator,
|
||||
telemetry Telemetry,
|
||||
observabilitySender observability.Sender,
|
||||
) *Service {
|
||||
return &Service{
|
||||
requests: cpc.NewCPC(),
|
||||
@ -85,6 +89,8 @@ func NewService(
|
||||
tasks: async.NewGroup(ctx, panicHandler),
|
||||
uidValidityGenerator: uidValidityGenerator,
|
||||
telemetry: telemetry,
|
||||
|
||||
observabilitySender: observabilitySender,
|
||||
}
|
||||
}
|
||||
|
||||
@ -445,10 +451,12 @@ func (sm *Service) createIMAPServer(ctx context.Context) (*gluon.Server, error)
|
||||
sm.reporter,
|
||||
sm.imapSettings.LogClient(),
|
||||
sm.imapSettings.LogServer(),
|
||||
sm.imapSettings.DisableIMAPAuthenticate(),
|
||||
sm.imapSettings.EventPublisher(),
|
||||
sm.tasks,
|
||||
sm.uidValidityGenerator,
|
||||
sm.panicHandler,
|
||||
sm.observabilitySender,
|
||||
)
|
||||
if err == nil {
|
||||
sm.eventPublisher.PublishEvent(ctx, events.IMAPServerCreated{})
|
||||
|
||||
@ -50,7 +50,6 @@ type Service struct {
|
||||
}
|
||||
|
||||
const bitfieldRegexPattern = `^\\\d+`
|
||||
const disableNotificationsKillSwitch = "InboxBridgeEventLoopNotificationDisabled"
|
||||
|
||||
func NewService(userID string, service userevents.Subscribable, eventPublisher events.EventPublisher, store *Store,
|
||||
getFlagFn unleash.GetFlagValueFn, observabilitySender observability.Sender) *Service {
|
||||
@ -103,7 +102,7 @@ func (s *Service) run(ctx context.Context) {
|
||||
}
|
||||
|
||||
func (s *Service) HandleNotificationEvents(ctx context.Context, notificationEvents []proton.NotificationEvent) error {
|
||||
if s.getFlagValueFn(disableNotificationsKillSwitch) {
|
||||
if s.getFlagValueFn(unleash.EventLoopNotificationDisabled) {
|
||||
s.log.Info("Received notification events. Skipping as kill switch is enabled.")
|
||||
return nil
|
||||
}
|
||||
|
||||
93
internal/services/observability/adapter.go
Normal file
93
internal/services/observability/adapter.go
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright (c) 2024 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package observability
|
||||
|
||||
import (
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
)
|
||||
|
||||
type Adapter struct {
|
||||
sender Sender
|
||||
}
|
||||
|
||||
func NewAdapter(sender Sender) *Adapter {
|
||||
return &Adapter{sender: sender}
|
||||
}
|
||||
|
||||
// VerifyAndParseGenericMetrics parses a metric provided as an interface into a proton.ObservabilityMetric type.
|
||||
// It's exported as it is also used in integration tests.
|
||||
func VerifyAndParseGenericMetrics(metric map[string]interface{}) (bool, proton.ObservabilityMetric) {
|
||||
name, ok := metric["Name"].(string)
|
||||
if !ok {
|
||||
return false, proton.ObservabilityMetric{}
|
||||
}
|
||||
|
||||
version, ok := metric["Version"].(int)
|
||||
if !ok {
|
||||
return false, proton.ObservabilityMetric{}
|
||||
}
|
||||
|
||||
timestamp, ok := metric["Timestamp"].(int64)
|
||||
if !ok {
|
||||
return false, proton.ObservabilityMetric{}
|
||||
}
|
||||
|
||||
data, ok := metric["Data"]
|
||||
if !ok {
|
||||
return false, proton.ObservabilityMetric{}
|
||||
}
|
||||
|
||||
return true, proton.ObservabilityMetric{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Timestamp: timestamp,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func (adapter *Adapter) AddMetrics(metrics ...map[string]interface{}) {
|
||||
var typedMetrics []proton.ObservabilityMetric
|
||||
|
||||
for _, metric := range metrics {
|
||||
if ok, m := VerifyAndParseGenericMetrics(metric); ok {
|
||||
typedMetrics = append(typedMetrics, m)
|
||||
}
|
||||
}
|
||||
|
||||
if len(typedMetrics) > 0 {
|
||||
adapter.sender.AddMetrics(typedMetrics...)
|
||||
}
|
||||
}
|
||||
|
||||
func (adapter *Adapter) AddDistinctMetrics(errType interface{}, metrics ...map[string]interface{}) {
|
||||
errTypeInt, ok := errType.(int)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var typedMetrics []proton.ObservabilityMetric
|
||||
for _, metric := range metrics {
|
||||
if ok, m := VerifyAndParseGenericMetrics(metric); ok {
|
||||
typedMetrics = append(typedMetrics, m)
|
||||
}
|
||||
}
|
||||
|
||||
if len(typedMetrics) > 0 {
|
||||
adapter.sender.AddDistinctMetrics(DistinctionErrorTypeEnum(errTypeInt), typedMetrics...)
|
||||
}
|
||||
}
|
||||
58
internal/services/observability/adapter_test.go
Normal file
58
internal/services/observability/adapter_test.go
Normal file
@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2024 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package observability
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_AdapterCustomMetrics(t *testing.T) {
|
||||
customMetric := map[string]interface{}{
|
||||
"Name": "name",
|
||||
"Version": 1,
|
||||
"Timestamp": time.Now().Unix(),
|
||||
"Data": map[string]interface{}{
|
||||
"Value": 1,
|
||||
"Labels": map[string]string{
|
||||
"error": "customError",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ok, metric := VerifyAndParseGenericMetrics(customMetric)
|
||||
require.True(t, ok)
|
||||
|
||||
require.Equal(t, metric.Name, customMetric["Name"])
|
||||
require.Equal(t, metric.Timestamp, customMetric["Timestamp"])
|
||||
require.Equal(t, metric.Version, customMetric["Version"])
|
||||
require.Equal(t, metric.Data, customMetric["Data"])
|
||||
}
|
||||
|
||||
func Test_AdapterGluonMetrics(t *testing.T) {
|
||||
metrics := GenerateAllGluonMetrics()
|
||||
|
||||
for _, metric := range metrics {
|
||||
ok, m := VerifyAndParseGenericMetrics(metric)
|
||||
fmt.Println(m)
|
||||
require.True(t, ok)
|
||||
}
|
||||
}
|
||||
@ -25,13 +25,21 @@ type DistinctionErrorTypeEnum int
|
||||
|
||||
const (
|
||||
SyncError DistinctionErrorTypeEnum = iota
|
||||
EventLoopError
|
||||
GluonImapError
|
||||
GluonMessageError
|
||||
GluonOtherError
|
||||
SMTPError
|
||||
EventLoopError // EventLoopError - should always be kept last when inserting new keys.
|
||||
)
|
||||
|
||||
// errorSchemaMap - maps between the DistinctionErrorTypeEnum and the relevant schema name.
|
||||
var errorSchemaMap = map[DistinctionErrorTypeEnum]string{ //nolint:gochecknoglobals
|
||||
SyncError: "bridge_sync_errors_users_total",
|
||||
EventLoopError: "bridge_event_loop_events_errors_users_total",
|
||||
SyncError: "bridge_sync_errors_users_total",
|
||||
EventLoopError: "bridge_event_loop_events_errors_users_total",
|
||||
GluonImapError: "bridge_gluon_imap_errors_users_total",
|
||||
GluonMessageError: "bridge_gluon_message_errors_users_total",
|
||||
SMTPError: "bridge_smtp_errors_users_total",
|
||||
GluonOtherError: "bridge_gluon_other_errors_users_total",
|
||||
}
|
||||
|
||||
// createLastSentMap - needs to be updated whenever we make changes to the enum.
|
||||
|
||||
@ -24,6 +24,7 @@ import (
|
||||
|
||||
"github.com/ProtonMail/gluon/async"
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/plan"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
||||
)
|
||||
|
||||
@ -62,7 +63,7 @@ func newDistinctionUtility(ctx context.Context, panicHandler async.PanicHandler,
|
||||
|
||||
observabilitySender: observabilitySender,
|
||||
|
||||
userPlanUnsafe: planUnknown,
|
||||
userPlanUnsafe: plan.Unknown,
|
||||
|
||||
heartbeatData: heartbeatData{},
|
||||
heartbeatTicker: time.NewTicker(updateInterval),
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
package observability
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/gluon/async"
|
||||
@ -26,26 +26,32 @@ import (
|
||||
)
|
||||
|
||||
const genericHeartbeatSchemaName = "bridge_generic_user_heartbeat_total"
|
||||
const genericHeartbeatVersion = 2
|
||||
|
||||
type heartbeatData struct {
|
||||
receivedSyncError bool
|
||||
receivedEventLoopError bool
|
||||
receivedOtherError bool
|
||||
receivedGluonError bool
|
||||
}
|
||||
|
||||
func (d *distinctionUtility) resetHeartbeatData() {
|
||||
d.heartbeatData.receivedSyncError = false
|
||||
d.heartbeatData.receivedOtherError = false
|
||||
d.heartbeatData.receivedEventLoopError = false
|
||||
d.heartbeatData.receivedGluonError = false
|
||||
}
|
||||
|
||||
func (d *distinctionUtility) updateHeartbeatData(errType DistinctionErrorTypeEnum) {
|
||||
d.withUpdateHeartbeatDataLock(func() {
|
||||
//nolint:exhaustive
|
||||
switch errType {
|
||||
case SyncError:
|
||||
d.heartbeatData.receivedSyncError = true
|
||||
case EventLoopError:
|
||||
d.heartbeatData.receivedEventLoopError = true
|
||||
case GluonMessageError, GluonImapError, GluonOtherError:
|
||||
d.heartbeatData.receivedGluonError = true
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -81,10 +87,6 @@ func (d *distinctionUtility) sendHeartbeat() {
|
||||
})
|
||||
}
|
||||
|
||||
func formatBool(value bool) string {
|
||||
return fmt.Sprintf("%t", value)
|
||||
}
|
||||
|
||||
// generateHeartbeatUserMetric creates the heartbeat user metric and includes the relevant data.
|
||||
func (d *distinctionUtility) generateHeartbeatUserMetric() proton.ObservabilityMetric {
|
||||
return generateHeartbeatMetric(
|
||||
@ -92,16 +94,17 @@ func (d *distinctionUtility) generateHeartbeatUserMetric() proton.ObservabilityM
|
||||
d.getEmailClientUserAgent(),
|
||||
getEnabled(d.settingsGetter.GetProxyAllowed()),
|
||||
getEnabled(d.getBetaAccessEnabled()),
|
||||
formatBool(d.heartbeatData.receivedOtherError),
|
||||
formatBool(d.heartbeatData.receivedSyncError),
|
||||
formatBool(d.heartbeatData.receivedEventLoopError),
|
||||
strconv.FormatBool(d.heartbeatData.receivedOtherError),
|
||||
strconv.FormatBool(d.heartbeatData.receivedSyncError),
|
||||
strconv.FormatBool(d.heartbeatData.receivedEventLoopError),
|
||||
strconv.FormatBool(d.heartbeatData.receivedGluonError),
|
||||
)
|
||||
}
|
||||
|
||||
func generateHeartbeatMetric(plan, mailClient, dohEnabled, betaAccess, otherError, syncError, eventLoopError string) proton.ObservabilityMetric {
|
||||
func generateHeartbeatMetric(plan, mailClient, dohEnabled, betaAccess, otherError, syncError, eventLoopError, gluonError string) proton.ObservabilityMetric {
|
||||
return proton.ObservabilityMetric{
|
||||
Name: genericHeartbeatSchemaName,
|
||||
Version: 1,
|
||||
Version: genericHeartbeatVersion,
|
||||
Timestamp: time.Now().Unix(),
|
||||
Data: map[string]interface{}{
|
||||
"Value": 1,
|
||||
@ -113,6 +116,7 @@ func generateHeartbeatMetric(plan, mailClient, dohEnabled, betaAccess, otherErro
|
||||
"receivedOtherError": otherError,
|
||||
"receivedSyncError": syncError,
|
||||
"receivedEventLoopError": eventLoopError,
|
||||
"receivedGluonError": gluonError,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -18,76 +18,9 @@
|
||||
package observability
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/gluon/async"
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/plan"
|
||||
)
|
||||
|
||||
const (
|
||||
planUnknown = "unknown"
|
||||
planOther = "other"
|
||||
planBusiness = "business"
|
||||
planIndividual = "individual"
|
||||
planGroup = "group"
|
||||
)
|
||||
|
||||
var planHierarchy = map[string]int{ //nolint:gochecknoglobals
|
||||
planBusiness: 4,
|
||||
planGroup: 3,
|
||||
planIndividual: 2,
|
||||
planOther: 1,
|
||||
planUnknown: 0,
|
||||
}
|
||||
|
||||
type planGetter interface {
|
||||
GetOrganizationData(ctx context.Context) (proton.OrganizationResponse, error)
|
||||
}
|
||||
|
||||
func isHigherPriority(currentPlan, newPlan string) bool {
|
||||
newRank, ok := planHierarchy[newPlan]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
currentRank, ok2 := planHierarchy[currentPlan]
|
||||
if !ok2 {
|
||||
return true // we don't have a valid plan, might as well replace it
|
||||
}
|
||||
|
||||
return newRank > currentRank
|
||||
}
|
||||
|
||||
func mapUserPlan(planName string) string {
|
||||
if planName == "" {
|
||||
return planUnknown
|
||||
}
|
||||
switch strings.TrimSpace(strings.ToLower(planName)) {
|
||||
case "mail2022":
|
||||
return planIndividual
|
||||
case "bundle2022":
|
||||
return planIndividual
|
||||
case "family2022":
|
||||
return planGroup
|
||||
case "visionary2022":
|
||||
return planGroup
|
||||
case "mailpro2022":
|
||||
return planBusiness
|
||||
case "planbiz2024":
|
||||
return planBusiness
|
||||
case "bundlepro2022":
|
||||
return planBusiness
|
||||
case "bundlepro2024":
|
||||
return planBusiness
|
||||
case "duo2024":
|
||||
return planGroup
|
||||
|
||||
default:
|
||||
return planOther
|
||||
}
|
||||
}
|
||||
|
||||
func (d *distinctionUtility) setUserPlan(planName string) {
|
||||
if planName == "" {
|
||||
return
|
||||
@ -96,24 +29,12 @@ func (d *distinctionUtility) setUserPlan(planName string) {
|
||||
d.userPlanLock.Lock()
|
||||
defer d.userPlanLock.Unlock()
|
||||
|
||||
userPlanMapped := mapUserPlan(planName)
|
||||
if isHigherPriority(d.userPlanUnsafe, userPlanMapped) {
|
||||
userPlanMapped := plan.MapUserPlan(planName)
|
||||
if plan.IsHigherPriority(d.userPlanUnsafe, userPlanMapped) {
|
||||
d.userPlanUnsafe = userPlanMapped
|
||||
}
|
||||
}
|
||||
|
||||
func (d *distinctionUtility) registerUserPlan(ctx context.Context, getter planGetter, panicHandler async.PanicHandler) {
|
||||
go func() {
|
||||
defer async.HandlePanic(panicHandler)
|
||||
|
||||
orgRes, err := getter.GetOrganizationData(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
d.setUserPlan(orgRes.Organization.PlanName)
|
||||
}()
|
||||
}
|
||||
|
||||
func (d *distinctionUtility) getUserPlanSafe() string {
|
||||
d.userPlanLock.Lock()
|
||||
defer d.userPlanLock.Unlock()
|
||||
|
||||
@ -250,7 +250,7 @@ func (s *Service) addMetricsIfClients(metric ...proton.ObservabilityMetric) {
|
||||
s.addMetrics(metric...)
|
||||
}
|
||||
|
||||
func (s *Service) RegisterUserClient(userID string, protonClient *proton.Client, telemetryService *telemetry.Service) {
|
||||
func (s *Service) RegisterUserClient(userID string, protonClient *proton.Client, telemetryService *telemetry.Service, userPlan string) {
|
||||
s.log.Info("Registering user client, ID:", userID)
|
||||
|
||||
s.withUserClientStoreLock(func() {
|
||||
@ -260,7 +260,7 @@ func (s *Service) RegisterUserClient(userID string, protonClient *proton.Client,
|
||||
}
|
||||
})
|
||||
|
||||
s.distinctionUtility.registerUserPlan(s.ctx, protonClient, s.panicHandler)
|
||||
s.distinctionUtility.setUserPlan(userPlan)
|
||||
|
||||
// There may be a case where we already have metric updates stored, so try to flush;
|
||||
s.sendSignal(s.signalDataArrived)
|
||||
|
||||
@ -18,16 +18,18 @@
|
||||
package observability
|
||||
|
||||
import (
|
||||
gluonMetrics "github.com/ProtonMail/gluon/observability/metrics"
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/plan"
|
||||
)
|
||||
|
||||
func GenerateAllUsedDistinctionMetricPermutations() []proton.ObservabilityMetric {
|
||||
planValues := []string{
|
||||
planUnknown,
|
||||
planOther,
|
||||
planBusiness,
|
||||
planIndividual,
|
||||
planGroup}
|
||||
plan.Unknown,
|
||||
plan.Other,
|
||||
plan.Business,
|
||||
plan.Individual,
|
||||
plan.Group}
|
||||
mailClientValues := []string{
|
||||
emailAgentAppleMail,
|
||||
emailAgentOutlook,
|
||||
@ -57,11 +59,11 @@ func GenerateAllUsedDistinctionMetricPermutations() []proton.ObservabilityMetric
|
||||
|
||||
func GenerateAllHeartbeatMetricPermutations() []proton.ObservabilityMetric {
|
||||
planValues := []string{
|
||||
planUnknown,
|
||||
planOther,
|
||||
planBusiness,
|
||||
planIndividual,
|
||||
planGroup}
|
||||
plan.Unknown,
|
||||
plan.Other,
|
||||
plan.Business,
|
||||
plan.Individual,
|
||||
plan.Group}
|
||||
mailClientValues := []string{
|
||||
emailAgentAppleMail,
|
||||
emailAgentOutlook,
|
||||
@ -85,16 +87,19 @@ func GenerateAllHeartbeatMetricPermutations() []proton.ObservabilityMetric {
|
||||
for _, receivedOtherError := range trueFalseValues {
|
||||
for _, receivedSyncError := range trueFalseValues {
|
||||
for _, receivedEventLoopError := range trueFalseValues {
|
||||
metrics = append(metrics,
|
||||
generateHeartbeatMetric(plan,
|
||||
mailClient,
|
||||
dohEnabled,
|
||||
betaAccess,
|
||||
receivedOtherError,
|
||||
receivedSyncError,
|
||||
receivedEventLoopError,
|
||||
),
|
||||
)
|
||||
for _, receivedGluonError := range trueFalseValues {
|
||||
metrics = append(metrics,
|
||||
generateHeartbeatMetric(plan,
|
||||
mailClient,
|
||||
dohEnabled,
|
||||
betaAccess,
|
||||
receivedOtherError,
|
||||
receivedSyncError,
|
||||
receivedEventLoopError,
|
||||
receivedGluonError,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -104,3 +109,20 @@ func GenerateAllHeartbeatMetricPermutations() []proton.ObservabilityMetric {
|
||||
}
|
||||
return metrics
|
||||
}
|
||||
|
||||
func GenerateAllGluonMetrics() []map[string]interface{} {
|
||||
var metrics []map[string]interface{}
|
||||
metrics = append(metrics,
|
||||
gluonMetrics.GenerateFailedParseIMAPCommandMetric(),
|
||||
gluonMetrics.GenerateFailedToCreateMailbox(),
|
||||
gluonMetrics.GenerateFailedToDeleteMailboxMetric(),
|
||||
gluonMetrics.GenerateFailedToCopyMessagesMetric(),
|
||||
gluonMetrics.GenerateFailedToMoveMessagesFromMailboxMetric(),
|
||||
gluonMetrics.GenerateFailedToRemoveDeletedMessagesMetric(),
|
||||
gluonMetrics.GenerateFailedToCommitDatabaseTransactionMetric(),
|
||||
gluonMetrics.GenerateAppendToDraftsMustNotReturnExistingRemoteID(),
|
||||
gluonMetrics.GenerateDatabaseMigrationFailed(),
|
||||
gluonMetrics.GenerateFailedToStoreFlagsOnMessages(),
|
||||
)
|
||||
return metrics
|
||||
}
|
||||
|
||||
@ -104,8 +104,3 @@ func TestMatchUserAgent(t *testing.T) {
|
||||
require.Equal(t, testCase.result, matchUserAgent(testCase.agent))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatBool(t *testing.T) {
|
||||
require.Equal(t, "false", formatBool(false))
|
||||
require.Equal(t, "true", formatBool(true))
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user