From 3b580785956c260f0781a1811aab91ea25543ae5 Mon Sep 17 00:00:00 2001 From: Leander Beernaert Date: Mon, 11 Sep 2023 15:44:11 +0200 Subject: [PATCH 01/93] fix(GODT-2929): Message dedup with different text transfer encoding https://github.com/ProtonMail/gluon/pull/396 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ad3f2697..2280031e 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 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.20230829112217-5d5c25c504b5 + github.com/ProtonMail/gluon v0.17.1-0.20230911134257-5eb2eeebbef5 github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a github.com/ProtonMail/go-proton-api v0.4.1-0.20230831064234-0e3a549b3f36 github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton diff --git a/go.sum b/go.sum index 5177f714..ba8ee115 100644 --- a/go.sum +++ b/go.sum @@ -23,8 +23,8 @@ github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo= github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk= github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g= -github.com/ProtonMail/gluon v0.17.1-0.20230829112217-5d5c25c504b5 h1:C/8P5NHAKi2yCKez+OZ5rSR8SsL7k8si4pK4SE2QtV8= -github.com/ProtonMail/gluon v0.17.1-0.20230829112217-5d5c25c504b5/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo= +github.com/ProtonMail/gluon v0.17.1-0.20230911134257-5eb2eeebbef5 h1:O4BusNL870VgVVDSUX2Oaz8A/fNtJhakUKwx0YBIdn8= +github.com/ProtonMail/gluon v0.17.1-0.20230911134257-5eb2eeebbef5/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo= github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4= github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4= github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= From 50dc5c40858f6ee9aaa67e17ce96b3d6ae2bf95e Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 12 Sep 2023 07:45:08 +0200 Subject: [PATCH 02/93] chore: Umshiang Bridge 3.5.0 changelog. --- Changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Changelog.md b/Changelog.md index 75db7f60..9f72ff55 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,8 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) ## Umshiang Bridge 3.5.0 ### Added +* GODT-2734: Add testing steps to modify account settings. +* GODT-2746: Integration tests for reporting a problem. * GODT-2891: Allow message create & delete during sync. * GODT-2848: Decouple IMAP service from Event Loop. * Add trace profiling option. @@ -19,6 +21,8 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) * GODT-2803: Bridge Database access. ### Changed +* GODT-2909: Remove Timeout on event publish. +* GODT-2913: Reduce the number of configuration failure detected. * GODT-2828: Increase sync progress report frequency. * Test: Fix TestBridge_SyncWithOnGoingEvents. * GODT-2871: Is telemetry enabled as service. From 8e5a892c45c23e3fabd37aac114aa88372558127 Mon Sep 17 00:00:00 2001 From: Jakub Date: Fri, 1 Sep 2023 08:16:17 +0200 Subject: [PATCH 03/93] feat(GODT-2664): trigger QA installer. --- .gitlab-ci.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9b3dbf59..1eb2fe88 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -282,4 +282,18 @@ build-windows-qa: variables: BUILD_TAGS: "build_qa" +trigeer-qa-installer: + stage: build + needs: ["lint"] + extends: + - .rules-branch-and-MR-manual + variables: + APP: bridge + WORKFLOW: build-all + SRC_TAG: $CI_COMMIT_BRANCH + SRC_HASH: $CI_COMMIT_SHA + trigger: + project: "jcuth/bridge-release" + branch: master + # TODO: PUT BACK ALL THE JOBS! JUST DID THIS FOR NOW TO GET CI WORKING AGAIN... From cab32d5d5a453ee0c70f63191d9c8e9f5be8dfe5 Mon Sep 17 00:00:00 2001 From: Jakub Date: Wed, 13 Sep 2023 10:25:47 +0200 Subject: [PATCH 04/93] chore: update changelog. --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 9f72ff55..918165f2 100644 --- a/Changelog.md +++ b/Changelog.md @@ -75,6 +75,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) * GODT-2780: Fix 'QSystemTrayIcon::setVisible: No Icon set' warning in bridge-gui log on startup. * GODT-2778: Fix login screen being disabled after an 'already logged in' error. * Fix typos found by codespell. +* GODT-2577: Answered flag should only be applied to replied messages. ## Trift Bridge 3.4.1 From fa794a982b68a4a4bfdb1a4003425dcbe06588db Mon Sep 17 00:00:00 2001 From: Romain Le Jeune Date: Fri, 15 Sep 2023 10:53:58 +0000 Subject: [PATCH 05/93] feat(GODT-2597): Implement contact specific settings in integration tests. --- COPYING_NOTES.md | 2 +- go.mod | 4 +- go.sum | 4 +- internal/services/smtp/smtp.go | 2 +- tests/contact_test.go | 448 ++++++++++++++++++++++++++++ tests/features/user/contact.feature | 65 ++++ tests/steps_test.go | 14 + tests/testdata/keys/pubkey.asc | 35 +++ tests/types_test.go | 9 + 9 files changed, 577 insertions(+), 6 deletions(-) create mode 100644 tests/contact_test.go create mode 100644 tests/features/user/contact.feature create mode 100644 tests/testdata/keys/pubkey.asc diff --git a/COPYING_NOTES.md b/COPYING_NOTES.md index f7f4d43a..1fc10d94 100644 --- a/COPYING_NOTES.md +++ b/COPYING_NOTES.md @@ -40,6 +40,7 @@ Proton Mail Bridge includes the following 3rd party software: * [go-message](https://github.com/emersion/go-message) available under [license](https://github.com/emersion/go-message/blob/master/LICENSE) * [go-sasl](https://github.com/emersion/go-sasl) available under [license](https://github.com/emersion/go-sasl/blob/master/LICENSE) * [go-smtp](https://github.com/emersion/go-smtp) available under [license](https://github.com/emersion/go-smtp/blob/master/LICENSE) +* [go-vcard](https://github.com/emersion/go-vcard) available under [license](https://github.com/emersion/go-vcard/blob/master/LICENSE) * [color](https://github.com/fatih/color) available under [license](https://github.com/fatih/color/blob/master/LICENSE) * [sentry-go](https://github.com/getsentry/sentry-go) available under [license](https://github.com/getsentry/sentry-go/blob/master/LICENSE) * [resty](https://github.com/go-resty/resty/v2) available under [license](https://github.com/go-resty/resty/v2/blob/master/LICENSE) @@ -83,7 +84,6 @@ Proton Mail Bridge includes the following 3rd party software: * [go-spew](https://github.com/davecgh/go-spew) available under [license](https://github.com/davecgh/go-spew/blob/master/LICENSE) * [go-windows](https://github.com/elastic/go-windows) available under [license](https://github.com/elastic/go-windows/blob/master/LICENSE) * [go-textwrapper](https://github.com/emersion/go-textwrapper) available under [license](https://github.com/emersion/go-textwrapper/blob/master/LICENSE) -* [go-vcard](https://github.com/emersion/go-vcard) available under [license](https://github.com/emersion/go-vcard/blob/master/LICENSE) * [fgprof](https://github.com/felixge/fgprof) available under [license](https://github.com/felixge/fgprof/blob/master/LICENSE) * [go-shlex](https://github.com/flynn-archive/go-shlex) available under [license](https://github.com/flynn-archive/go-shlex/blob/master/LICENSE) * [mimetype](https://github.com/gabriel-vasile/mimetype) available under [license](https://github.com/gabriel-vasile/mimetype/blob/master/LICENSE) diff --git a/go.mod b/go.mod index 2280031e..d5882959 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Masterminds/semver/v3 v3.2.0 github.com/ProtonMail/gluon v0.17.1-0.20230911134257-5eb2eeebbef5 github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a - github.com/ProtonMail/go-proton-api v0.4.1-0.20230831064234-0e3a549b3f36 + github.com/ProtonMail/go-proton-api v0.4.1-0.20230915070741-3de73982c764 github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton github.com/PuerkitoBio/goquery v1.8.1 github.com/abiosoft/ishell v2.0.0+incompatible @@ -22,6 +22,7 @@ require ( github.com/emersion/go-message v0.16.0 github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead github.com/emersion/go-smtp v0.15.1-0.20221021114529-49b17434419d + github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3 github.com/fatih/color v1.13.0 github.com/getsentry/sentry-go v0.15.0 github.com/go-resty/resty/v2 v2.7.0 @@ -68,7 +69,6 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/elastic/go-windows v1.0.1 // indirect github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect - github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3 // indirect github.com/felixge/fgprof v0.9.3 // indirect github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect diff --git a/go.sum b/go.sum index ba8ee115..034e0724 100644 --- a/go.sum +++ b/go.sum @@ -35,8 +35,8 @@ github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7 h1:+j+Kd/ github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= -github.com/ProtonMail/go-proton-api v0.4.1-0.20230831064234-0e3a549b3f36 h1:JVMK2w90bCWayUCXJIb3wkQ5+j2P/NbnrX3BrDoLzsc= -github.com/ProtonMail/go-proton-api v0.4.1-0.20230831064234-0e3a549b3f36/go.mod h1:nS8hMGjJLgC0Iej0JMYbsI388LesEkM1Hj/jCCxQeaQ= +github.com/ProtonMail/go-proton-api v0.4.1-0.20230915070741-3de73982c764 h1:2rEmoo5BgEap+9Y484xAL8cod1bbjDaeWaGFLS/a1Ec= +github.com/ProtonMail/go-proton-api v0.4.1-0.20230915070741-3de73982c764/go.mod h1:nS8hMGjJLgC0Iej0JMYbsI388LesEkM1Hj/jCCxQeaQ= github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI= github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk= github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton h1:YS6M20yvjCJPR1r4ADW5TPn6rahs4iAyZaACei86bEc= diff --git a/internal/services/smtp/smtp.go b/internal/services/smtp/smtp.go index 58730228..b26fd143 100644 --- a/internal/services/smtp/smtp.go +++ b/internal/services/smtp/smtp.go @@ -535,7 +535,7 @@ func getContactSettings( return proton.ContactSettings{}, fmt.Errorf("failed to get contact: %w", err) } - return contact.GetSettings(userKR, recipient) + return contact.GetSettings(userKR, recipient, proton.CardTypeSigned) } func getMessageSender(parser *parser.Parser) (string, bool) { diff --git a/tests/contact_test.go b/tests/contact_test.go new file mode 100644 index 00000000..aeab60ae --- /dev/null +++ b/tests/contact_test.go @@ -0,0 +1,448 @@ +// Copyright (c) 2023 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 . + +package tests + +import ( + "context" + "errors" + "os" + + "github.com/ProtonMail/gluon/rfc822" + "github.com/ProtonMail/go-proton-api" + "github.com/ProtonMail/gopenpgp/v2/crypto" + "github.com/cucumber/godog" + "github.com/emersion/go-vcard" +) + +func (s *scenario) userHasContacts(user string, contacts *godog.Table) error { + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + contactList, err := unmarshalTable[Contact](contacts) + if err != nil { + return err + } + for _, contact := range contactList { + var settings = proton.ContactSettings{} + format, err := stringToMimeType(contact.Format) + if err != nil { + settings.MIMEType = nil + } else { + settings.SetMimeType(format) + } + scheme, err := stringToEncryptionScheme(contact.Scheme) + if err != nil { + settings.Scheme = nil + } else { + settings.SetScheme(scheme) + } + sign, err := stringToBool(contact.Sign) + if err != nil { + settings.Sign = nil + } else { + settings.SetSign(sign) + } + encrypt, err := stringToBool(contact.Encrypt) + if err != nil { + settings.Encrypt = nil + } else { + settings.SetEncrypt(encrypt) + } + if err := createContact(ctx, c, contact.Email, contact.Name, addrKR, &settings); err != nil { + return err + } + } + return nil + }) + }) +} + +func (s *scenario) userHasContactWithName(user, contact, name string) error { + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + return createContact(ctx, c, contact, name, addrKR, nil) + }) + }) +} + +func (s *scenario) contactOfUserHasNoMessageFormat(email, user string) error { + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + contact, err := getContact(ctx, c, email) + if err != nil { + return err + } + for _, card := range contact.Cards { + settings, err := contact.GetSettings(addrKR, email, card.Type) + if err != nil { + return err + } + settings.MIMEType = nil + + err = contact.SetSettings(addrKR, email, card.Type, settings) + if err != nil { + return err + } + } + _, err = c.UpdateContact(ctx, contact.ContactMetadata.ID, proton.UpdateContactReq{Cards: contact.Cards}) + return err + }) + }) +} + +func (s *scenario) contactOfUserHasMessageFormat(email, user, format string) error { + value, err := stringToMimeType(format) + if err != nil { + return err + } + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + contact, err := getContact(ctx, c, email) + if err != nil { + return err + } + for _, card := range contact.Cards { + settings, err := contact.GetSettings(addrKR, email, card.Type) + if err != nil { + return err + } + settings.SetMimeType(value) + + err = contact.SetSettings(addrKR, email, card.Type, settings) + if err != nil { + return err + } + } + _, err = c.UpdateContact(ctx, contact.ContactMetadata.ID, proton.UpdateContactReq{Cards: contact.Cards}) + return err + }) + }) +} + +func (s *scenario) contactOfUserHasNoEncryptionScheme(email, user string) error { + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + contact, err := getContact(ctx, c, email) + if err != nil { + return err + } + for _, card := range contact.Cards { + settings, err := contact.GetSettings(addrKR, email, card.Type) + if err != nil { + return err + } + settings.Scheme = nil + + err = contact.SetSettings(addrKR, email, card.Type, settings) + if err != nil { + return err + } + } + _, err = c.UpdateContact(ctx, contact.ContactMetadata.ID, proton.UpdateContactReq{Cards: contact.Cards}) + return err + }) + }) +} + +func (s *scenario) contactOfUserHasEncryptionScheme(email, user, scheme string) error { + value := proton.PGPInlineScheme + switch { + case scheme == "inline": + value = proton.PGPInlineScheme + case scheme == "MIME": + value = proton.PGPMIMEScheme + default: + return errors.New("parameter should either be 'inline' or 'MIME'") + } + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + contact, err := getContact(ctx, c, email) + if err != nil { + return err + } + for _, card := range contact.Cards { + settings, err := contact.GetSettings(addrKR, email, card.Type) + if err != nil { + return err + } + settings.SetScheme(value) + + err = contact.SetSettings(addrKR, email, card.Type, settings) + if err != nil { + return err + } + } + _, err = c.UpdateContact(ctx, contact.ContactMetadata.ID, proton.UpdateContactReq{Cards: contact.Cards}) + return err + }) + }) +} + +func (s *scenario) contactOfUserHasNoSignature(email, user string) error { + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + contact, err := getContact(ctx, c, email) + if err != nil { + return err + } + for _, card := range contact.Cards { + settings, err := contact.GetSettings(addrKR, email, card.Type) + if err != nil { + return err + } + settings.Sign = nil + + err = contact.SetSettings(addrKR, email, card.Type, settings) + if err != nil { + return err + } + } + _, err = c.UpdateContact(ctx, contact.ContactMetadata.ID, proton.UpdateContactReq{Cards: contact.Cards}) + return err + }) + }) +} + +func (s *scenario) contactOfUserHasSignature(email, user, enabled string) error { + value := true + switch { + case enabled == "enabled": + value = true + case enabled == "disabled": + value = false + default: + return errors.New("parameter should either be 'enabled' or 'disabled'") + } + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + contact, err := getContact(ctx, c, email) + if err != nil { + return err + } + for _, card := range contact.Cards { + settings, err := contact.GetSettings(addrKR, email, card.Type) + if err != nil { + return err + } + settings.SetSign(value) + + err = contact.SetSettings(addrKR, email, card.Type, settings) + if err != nil { + return err + } + } + _, err = c.UpdateContact(ctx, contact.ContactMetadata.ID, proton.UpdateContactReq{Cards: contact.Cards}) + return err + }) + }) +} + +func (s *scenario) contactOfUserHasNoEncryption(email, user string) error { + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + contact, err := getContact(ctx, c, email) + if err != nil { + return err + } + for _, card := range contact.Cards { + settings, err := contact.GetSettings(addrKR, email, card.Type) + if err != nil { + return err + } + settings.Encrypt = nil + + err = contact.SetSettings(addrKR, email, card.Type, settings) + if err != nil { + return err + } + } + _, err = c.UpdateContact(ctx, contact.ContactMetadata.ID, proton.UpdateContactReq{Cards: contact.Cards}) + return err + }) + }) +} + +func (s *scenario) contactOfUserHasEncryption(email, user, enabled string) error { + value := true + switch { + case enabled == "enabled": + value = true + case enabled == "disabled": + value = false + default: + return errors.New("parameter should either be 'enabled' or 'disabled'") + } + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + contact, err := getContact(ctx, c, email) + if err != nil { + return err + } + for _, card := range contact.Cards { + settings, err := contact.GetSettings(addrKR, email, card.Type) + if err != nil { + return err + } + settings.SetEncrypt(value) + + err = contact.SetSettings(addrKR, email, card.Type, settings) + if err != nil { + return err + } + } + _, err = c.UpdateContact(ctx, contact.ContactMetadata.ID, proton.UpdateContactReq{Cards: contact.Cards}) + return err + }) + }) +} + +func (s *scenario) contactOfUserHasPubKey(email, user string, pubKey *godog.DocString) error { + return s.addContactKey(email, user, pubKey.Content) +} + +func (s *scenario) contactOfUserHasPubKeyFromFile(email, user, file string) error { + body, err := os.ReadFile(file) + if err != nil { + return err + } + return s.addContactKey(email, user, string(body)) +} + +func getContact(ctx context.Context, c *proton.Client, email string) (proton.Contact, error) { + contacts, err := c.GetAllContactEmails(ctx, email) + if err != nil { + return proton.Contact{}, err + } + if len(contacts) == 0 { + return proton.Contact{}, errors.New("No contact found with email " + email) + } + return c.GetContact(ctx, contacts[0].ContactID) +} + +func createContact(ctx context.Context, c *proton.Client, contact, name string, addrKR *crypto.KeyRing, settings *proton.ContactSettings) error { + card, err := proton.NewCard(addrKR, proton.CardTypeSigned) + if err != nil { + return err + } + if err := card.Set(addrKR, vcard.FieldUID, &vcard.Field{Value: "proton-legacy-139892c2-f691-4118-8c29-061196013e04", Group: "test"}); err != nil { + return err + } + + if err := card.Set(addrKR, vcard.FieldFormattedName, &vcard.Field{Value: name, Group: "test"}); err != nil { + return err + } + if err := card.Set(addrKR, vcard.FieldEmail, &vcard.Field{Value: contact, Group: "test"}); err != nil { + return err + } + res, err := c.CreateContacts(ctx, proton.CreateContactsReq{Contacts: []proton.ContactCards{{Cards: []*proton.Card{card}}}, Overwrite: 1}) + if err != nil { + return err + } + if res[0].Response.APIError.Code != proton.SuccessCode { + return errors.New("APIError " + res[0].Response.APIError.Message + " while creating contact") + } + + if settings != nil { + ctact, err := getContact(ctx, c, contact) + if err != nil { + return err + } + for _, card := range ctact.Cards { + settings, err := ctact.GetSettings(addrKR, contact, card.Type) + if err != nil { + return err + } + + err = ctact.SetSettings(addrKR, contact, card.Type, settings) + if err != nil { + return err + } + } + } + + return nil +} + +func (s *scenario) addContactKey(email, user string, pubKey string) error { + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + contact, err := getContact(ctx, c, email) + if err != nil { + return err + } + for _, card := range contact.Cards { + settings, err := contact.GetSettings(addrKR, email, card.Type) + if err != nil { + return err + } + key, err := crypto.NewKeyFromArmored(pubKey) + if err != nil { + return err + } + settings.AddKey(key) + + err = contact.SetSettings(addrKR, email, card.Type, settings) + if err != nil { + return err + } + } + _, err = c.UpdateContact(ctx, contact.ContactMetadata.ID, proton.UpdateContactReq{Cards: contact.Cards}) + return err + }) + }) +} + +func stringToMimeType(value string) (rfc822.MIMEType, error) { + switch { + case value == "plain": + return rfc822.TextPlain, nil + case value == "HTML": + return rfc822.TextHTML, nil + } + return rfc822.TextPlain, errors.New("parameter should either be 'plain' or 'HTML'") +} + +func stringToEncryptionScheme(value string) (proton.EncryptionScheme, error) { + switch { + case value == "inline": + return proton.PGPInlineScheme, nil + case value == "MIME": + return proton.PGPMIMEScheme, nil + } + return proton.PGPInlineScheme, errors.New("parameter should either be 'inline' or 'MIME'") +} + +func stringToBool(value string) (bool, error) { + switch { + case value == "enabled": + return true, nil + case value == "disabled": + return false, nil + } + return false, errors.New("parameter should either be 'enabled' or 'disabled'") +} diff --git a/tests/features/user/contact.feature b/tests/features/user/contact.feature new file mode 100644 index 00000000..08ce8643 --- /dev/null +++ b/tests/features/user/contact.feature @@ -0,0 +1,65 @@ +Feature: user's contact + Background: + Given there exists an account with username "[user:user]" and password "password" + And user "[user:user]" has contact "SuperTester@proton.me" with name "Super TESTER" + And user "[user:user]" has contacts: + | name | email | format | scheme | signature | encryption | + | Tester One | tester1@proton.me | plain | MIME | enabled | enabled | + | Tester Two | tester2@proton.me | HTML | inline | disabled | disabled | + Then it succeeds + When bridge starts + And the user logs in with username "[user:user]" and password "password" + Then it succeeds + + + Scenario: Playing with contact settings + When the contact "SuperTester@proton.me" of user "[user:user]" has message format "plain" + When the contact "SuperTester@proton.me" of user "[user:user]" has message format "HTML" + When the contact "SuperTester@proton.me" of user "[user:user]" has encryption scheme "inline" + When the contact "SuperTester@proton.me" of user "[user:user]" has encryption scheme "MIME" + When the contact "SuperTester@proton.me" of user "[user:user]" has no signature + When the contact "SuperTester@proton.me" of user "[user:user]" has no encryption + When the contact "SuperTester@proton.me" of user "[user:user]" has signature "enabled" + When the contact "SuperTester@proton.me" of user "[user:user]" has encryption "enabled" + When the contact "SuperTester@proton.me" of user "[user:user]" has signature "disabled" + When the contact "SuperTester@proton.me" of user "[user:user]" has encryption "disabled" + When the contact "SuperTester@proton.me" of user "[user:user]" has public key from file "testdata/keys/pubkey.asc" + When the contact "SuperTester@proton.me" of user "[user:user]" has public key: + """ + -----BEGIN PGP PUBLIC KEY BLOCK----- + + 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 + =9sH1 + -----END PGP PUBLIC KEY BLOCK----- + """ + diff --git a/tests/steps_test.go b/tests/steps_test.go index afe452b8..f987c70a 100644 --- a/tests/steps_test.go +++ b/tests/steps_test.go @@ -194,4 +194,18 @@ func (s *scenario) steps(ctx *godog.ScenarioContext) { ctx.Step(`^config status event "([^"]*)" is eventually send (\d+) time`, s.configStatusEventIsEventuallySendXTime) ctx.Step(`^config status event "([^"]*)" is not send more than (\d+) time`, s.configStatusEventIsNotSendMoreThanXTime) ctx.Step(`^force config status progress to be sent for user"([^"]*)"$`, s.forceConfigStatusProgressToBeSentForUser) + + // ==== CONTACT ==== + ctx.Step(`^user "([^"]*)" has contact "([^"]*)" with name "([^"]*)"$`, s.userHasContactWithName) + ctx.Step(`^user "([^"]*)" has contacts:$`, s.userHasContacts) + ctx.Step(`^the contact "([^"]*)" of user "([^"]*)" has no message format$`, s.contactOfUserHasNoMessageFormat) + ctx.Step(`^the contact "([^"]*)" of user "([^"]*)" has message format "([^"]*)"$`, s.contactOfUserHasMessageFormat) + ctx.Step(`^the contact "([^"]*)" of user "([^"]*)" has no encryption scheme$`, s.contactOfUserHasNoEncryptionScheme) + ctx.Step(`^the contact "([^"]*)" of user "([^"]*)" has encryption scheme "([^"]*)"$`, s.contactOfUserHasEncryptionScheme) + ctx.Step(`^the contact "([^"]*)" of user "([^"]*)" has no signature$`, s.contactOfUserHasNoSignature) + ctx.Step(`^the contact "([^"]*)" of user "([^"]*)" has signature "([^"]*)"$`, s.contactOfUserHasSignature) + ctx.Step(`^the contact "([^"]*)" of user "([^"]*)" has no encryption$`, s.contactOfUserHasNoEncryption) + ctx.Step(`^the contact "([^"]*)" of user "([^"]*)" has encryption "([^"]*)"$`, s.contactOfUserHasEncryption) + ctx.Step(`^the contact "([^"]*)" of user "([^"]*)" has public key:$`, s.contactOfUserHasPubKey) + ctx.Step(`^the contact "([^"]*)" of user "([^"]*)" has public key from file "([^"]*)"$`, s.contactOfUserHasPubKeyFromFile) } diff --git a/tests/testdata/keys/pubkey.asc b/tests/testdata/keys/pubkey.asc new file mode 100644 index 00000000..a66987af --- /dev/null +++ b/tests/testdata/keys/pubkey.asc @@ -0,0 +1,35 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=9sH1 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/types_test.go b/tests/types_test.go index e654635d..27b7e982 100644 --- a/tests/types_test.go +++ b/tests/types_test.go @@ -327,3 +327,12 @@ func mustParseBool(s string) bool { return v } + +type Contact struct { + Name string `bdd:"name"` + Email string `bdd:"email"` + Format string `bdd:"format"` + Scheme string `bdd:"scheme"` + Sign string `bdd:"signature"` + Encrypt string `bdd:"encryption"` +} From 03c340404427263cdb9821a5b27ec2fa962fa182 Mon Sep 17 00:00:00 2001 From: Leander Beernaert Date: Mon, 18 Sep 2023 14:38:51 +0200 Subject: [PATCH 06/93] chore(GODT-2916): Split Decryption from Message Building This helps the export tool to deal with problems arising from message assembly after everything has been successfully encrypted. The original behavior is still available under `DecryptAndBuildRFC822`. --- internal/services/imapservice/connector.go | 6 +- internal/services/imapservice/sync_build.go | 2 +- .../imapservice/sync_message_builder.go | 2 +- pkg/message/build.go | 165 ++++++------------ pkg/message/build_custom.go | 6 +- pkg/message/build_test.go | 98 +++++------ pkg/message/decrypt.go | 88 ++++++++++ pkg/message/decrypt_and_build.go | 40 +++++ 8 files changed, 240 insertions(+), 167 deletions(-) create mode 100644 pkg/message/decrypt.go create mode 100644 pkg/message/decrypt_and_build.go diff --git a/internal/services/imapservice/connector.go b/internal/services/imapservice/connector.go index 03ca7dbe..475bbfa6 100644 --- a/internal/services/imapservice/connector.go +++ b/internal/services/imapservice/connector.go @@ -159,7 +159,7 @@ func (s *Connector) GetMessageLiteral(ctx context.Context, id imap.MessageID) ([ var literal []byte err = s.identityState.WithAddrKR(msg.AddressID, func(_, addrKR *crypto.KeyRing) error { - l, buildErr := message.BuildRFC822(addrKR, msg.Message, msg.AttData, defaultMessageJobOpts()) + l, buildErr := message.DecryptAndBuildRFC822(addrKR, msg.Message, msg.AttData, defaultMessageJobOpts()) if buildErr != nil { return buildErr } @@ -249,7 +249,7 @@ func (s *Connector) CreateMessage(ctx context.Context, _ connector.IMAPStateWrit if err := s.identityState.WithAddrKR(full.AddressID, func(_, addrKR *crypto.KeyRing) error { var err error - if literal, err = message.BuildRFC822(addrKR, full.Message, full.AttData, defaultMessageJobOpts()); err != nil { + if literal, err = message.DecryptAndBuildRFC822(addrKR, full.Message, full.AttData, defaultMessageJobOpts()); err != nil { return err } @@ -611,7 +611,7 @@ func (s *Connector) importMessage( return fmt.Errorf("failed to fetch message: %w", err) } - if literal, err = message.BuildRFC822(addrKR, full.Message, full.AttData, defaultMessageJobOpts()); err != nil { + if literal, err = message.DecryptAndBuildRFC822(addrKR, full.Message, full.AttData, defaultMessageJobOpts()); err != nil { return fmt.Errorf("failed to build message: %w", err) } diff --git a/internal/services/imapservice/sync_build.go b/internal/services/imapservice/sync_build.go index 1af8e407..fe7348cb 100644 --- a/internal/services/imapservice/sync_build.go +++ b/internal/services/imapservice/sync_build.go @@ -57,7 +57,7 @@ func buildRFC822(apiLabels map[string]proton.Label, full proton.FullMessage, add buffer.Grow(full.Size) - if buildErr := message.BuildRFC822Into(addrKR, full.Message, full.AttData, defaultMessageJobOpts(), buffer); buildErr != nil { + if buildErr := message.DecryptAndBuildRFC822Into(addrKR, full.Message, full.AttData, defaultMessageJobOpts(), buffer); buildErr != nil { update = newMessageCreatedFailedUpdate(apiLabels, full.MessageMetadata, buildErr) err = buildErr } else if created, parseErr := newMessageCreatedUpdate(apiLabels, full.MessageMetadata, buffer.Bytes()); parseErr != nil { diff --git a/internal/services/imapservice/sync_message_builder.go b/internal/services/imapservice/sync_message_builder.go index 45adbb61..a0a006e2 100644 --- a/internal/services/imapservice/sync_message_builder.go +++ b/internal/services/imapservice/sync_message_builder.go @@ -46,7 +46,7 @@ func (s SyncMessageBuilder) BuildMessage( ) (syncservice.BuildResult, error) { buffer.Grow(full.Size) - if err := message.BuildRFC822Into(addrKR, full.Message, full.AttData, defaultMessageJobOpts(), buffer); err != nil { + if err := message.DecryptAndBuildRFC822Into(addrKR, full.Message, full.AttData, defaultMessageJobOpts(), buffer); err != nil { return syncservice.BuildResult{}, err } diff --git a/pkg/message/build.go b/pkg/message/build.go index baf916ee..e79f287c 100644 --- a/pkg/message/build.go +++ b/pkg/message/build.go @@ -19,8 +19,6 @@ package message import ( "bytes" - "encoding/base64" - "io" "mime" "net/mail" "strings" @@ -47,48 +45,36 @@ var ( // InternalIDDomain is used as a placeholder for reference/message ID headers to improve compatibility with various clients. const InternalIDDomain = `protonmail.internalid` -func BuildRFC822(kr *crypto.KeyRing, msg proton.Message, attData [][]byte, opts JobOptions) ([]byte, error) { - buf := new(bytes.Buffer) - if err := BuildRFC822Into(kr, msg, attData, opts, buf); err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -func BuildRFC822Into(kr *crypto.KeyRing, msg proton.Message, attData [][]byte, opts JobOptions, buf *bytes.Buffer) error { +func BuildRFC822Into(kr *crypto.KeyRing, decrypted *DecryptedMessage, opts JobOptions, buf *bytes.Buffer) error { switch { - case len(msg.Attachments) > 0: - return buildMultipartRFC822(kr, msg, attData, opts, buf) + case len(decrypted.Msg.Attachments) > 0: + return buildMultipartRFC822(decrypted, opts, buf) - case msg.MIMEType == "multipart/mixed": - return buildPGPRFC822(kr, msg, opts, buf) + case decrypted.Msg.MIMEType == "multipart/mixed": + return buildPGPRFC822(kr, decrypted, opts, buf) default: - return buildSimpleRFC822(kr, msg, opts, buf) + return buildSimpleRFC822(decrypted, opts, buf) } } -func buildSimpleRFC822(kr *crypto.KeyRing, msg proton.Message, opts JobOptions, buf *bytes.Buffer) error { - var decrypted bytes.Buffer - decrypted.Grow(len(msg.Body)) - - if err := msg.DecryptInto(kr, &decrypted); err != nil { +func buildSimpleRFC822(decrypted *DecryptedMessage, opts JobOptions, buf *bytes.Buffer) error { + if decrypted.BodyErr != nil { if !opts.IgnoreDecryptionErrors { - return errors.Wrap(ErrDecryptionFailed, err.Error()) + return decrypted.BodyErr } - return buildMultipartRFC822(kr, msg, nil, opts, buf) + return buildMultipartRFC822(decrypted, opts, buf) } - hdr := getTextPartHeader(getMessageHeader(msg, opts), decrypted.Bytes(), msg.MIMEType) + hdr := getTextPartHeader(getMessageHeader(decrypted.Msg, opts), decrypted.Body.Bytes(), decrypted.Msg.MIMEType) w, err := message.CreateWriter(buf, hdr) if err != nil { return err } - if _, err := w.Write(decrypted.Bytes()); err != nil { + if _, err := w.Write(decrypted.Body.Bytes()); err != nil { return err } @@ -96,15 +82,13 @@ func buildSimpleRFC822(kr *crypto.KeyRing, msg proton.Message, opts JobOptions, } func buildMultipartRFC822( - kr *crypto.KeyRing, - msg proton.Message, - attData [][]byte, + decrypted *DecryptedMessage, opts JobOptions, buf *bytes.Buffer, ) error { - boundary := newBoundary(msg.ID) + boundary := newBoundary(decrypted.Msg.ID) - hdr := getMessageHeader(msg, opts) + hdr := getMessageHeader(decrypted.Msg, opts) hdr.SetContentType("multipart/mixed", map[string]string{"boundary": boundary.gen()}) @@ -115,31 +99,31 @@ func buildMultipartRFC822( var ( inlineAtts []proton.Attachment - inlineData [][]byte + inlineData []DecryptedAttachment attachAtts []proton.Attachment - attachData [][]byte + attachData []DecryptedAttachment ) - for index, att := range msg.Attachments { + for index, att := range decrypted.Msg.Attachments { if att.Disposition == proton.InlineDisposition { inlineAtts = append(inlineAtts, att) - inlineData = append(inlineData, attData[index]) + inlineData = append(inlineData, decrypted.Attachments[index]) } else { attachAtts = append(attachAtts, att) - attachData = append(attachData, attData[index]) + attachData = append(attachData, decrypted.Attachments[index]) } } if len(inlineAtts) > 0 { - if err := writeRelatedParts(w, kr, boundary, msg, inlineAtts, inlineData, opts); err != nil { + if err := writeRelatedParts(w, boundary, decrypted, inlineAtts, inlineData, opts); err != nil { return err } - } else if err := writeTextPart(w, kr, msg, opts); err != nil { + } else if err := writeTextPart(w, decrypted, opts); err != nil { return err } for i, att := range attachAtts { - if err := writeAttachmentPart(w, kr, att, attachData[i], opts); err != nil { + if err := writeAttachmentPart(w, att, attachData[i], opts); err != nil { return err } } @@ -149,89 +133,53 @@ func buildMultipartRFC822( func writeTextPart( w *message.Writer, - kr *crypto.KeyRing, - msg proton.Message, + decrypted *DecryptedMessage, opts JobOptions, ) error { - var decrypted bytes.Buffer - decrypted.Grow(len(msg.Body)) - - if err := msg.DecryptInto(kr, &decrypted); err != nil { + if decrypted.BodyErr != nil { if !opts.IgnoreDecryptionErrors { - return errors.Wrap(ErrDecryptionFailed, err.Error()) + return decrypted.BodyErr } - return writeCustomTextPart(w, msg, err) + return writeCustomTextPart(w, decrypted, decrypted.BodyErr) } - return writePart(w, getTextPartHeader(message.Header{}, decrypted.Bytes(), msg.MIMEType), decrypted.Bytes()) + return writePart(w, getTextPartHeader(message.Header{}, decrypted.Body.Bytes(), decrypted.Msg.MIMEType), decrypted.Body.Bytes()) } func writeAttachmentPart( w *message.Writer, - kr *crypto.KeyRing, att proton.Attachment, - attData []byte, + decryptedAttachment DecryptedAttachment, opts JobOptions, ) error { - kps, err := base64.StdEncoding.DecodeString(att.KeyPackets) - if err != nil { - return err - } - - // Use io.Multi - attachmentReader := io.MultiReader(bytes.NewReader(kps), bytes.NewReader(attData)) - - stream, err := kr.DecryptStream(attachmentReader, nil, crypto.GetUnixTime()) - if err != nil { + if decryptedAttachment.Err != nil { if !opts.IgnoreDecryptionErrors { - return errors.Wrap(ErrDecryptionFailed, err.Error()) + return decryptedAttachment.Err } log. WithField("attID", att.ID). - WithError(err). - Warn("Attachment decryption failed - construct") + WithError(decryptedAttachment.Err). + Warn("Attachment decryption failed") var pgpMessageBuffer bytes.Buffer - pgpMessageBuffer.Grow(len(kps) + len(attData)) - pgpMessageBuffer.Write(kps) - pgpMessageBuffer.Write(attData) + pgpMessageBuffer.Grow(len(decryptedAttachment.Packet) + len(decryptedAttachment.Encrypted)) + pgpMessageBuffer.Write(decryptedAttachment.Packet) + pgpMessageBuffer.Write(decryptedAttachment.Encrypted) - return writeCustomAttachmentPart(w, att, &crypto.PGPMessage{Data: pgpMessageBuffer.Bytes()}, err) + return writeCustomAttachmentPart(w, att, &crypto.PGPMessage{Data: pgpMessageBuffer.Bytes()}, decryptedAttachment.Err) } - var decryptBuffer bytes.Buffer - decryptBuffer.Grow(len(kps) + len(attData)) - - if _, err := decryptBuffer.ReadFrom(stream); err != nil { - if !opts.IgnoreDecryptionErrors { - return errors.Wrap(ErrDecryptionFailed, err.Error()) - } - - log. - WithField("attID", att.ID). - WithError(err). - Warn("Attachment decryption failed - stream") - - var pgpMessageBuffer bytes.Buffer - pgpMessageBuffer.Grow(len(kps) + len(attData)) - pgpMessageBuffer.Write(kps) - pgpMessageBuffer.Write(attData) - - return writeCustomAttachmentPart(w, att, &crypto.PGPMessage{Data: pgpMessageBuffer.Bytes()}, err) - } - - return writePart(w, getAttachmentPartHeader(att), decryptBuffer.Bytes()) + return writePart(w, getAttachmentPartHeader(att), decryptedAttachment.Data.Bytes()) } func writeRelatedParts( w *message.Writer, - kr *crypto.KeyRing, boundary *boundary, - msg proton.Message, + decrypted *DecryptedMessage, atts []proton.Attachment, - attData [][]byte, + attData []DecryptedAttachment, opts JobOptions, ) error { hdr := message.Header{} @@ -239,12 +187,12 @@ func writeRelatedParts( hdr.SetContentType("multipart/related", map[string]string{"boundary": boundary.gen()}) return createPart(w, hdr, func(rel *message.Writer) error { - if err := writeTextPart(rel, kr, msg, opts); err != nil { + if err := writeTextPart(rel, decrypted, opts); err != nil { return err } for i, att := range atts { - if err := writeAttachmentPart(rel, kr, att, attData[i], opts); err != nil { + if err := writeAttachmentPart(rel, att, attData[i], opts); err != nil { return err } } @@ -253,37 +201,34 @@ func writeRelatedParts( }) } -func buildPGPRFC822(kr *crypto.KeyRing, msg proton.Message, opts JobOptions, buf *bytes.Buffer) error { - var decrypted bytes.Buffer - decrypted.Grow(len(msg.Body)) - - if err := msg.DecryptInto(kr, &decrypted); err != nil { +func buildPGPRFC822(kr *crypto.KeyRing, decrypted *DecryptedMessage, opts JobOptions, buf *bytes.Buffer) error { + if decrypted.BodyErr != nil { if !opts.IgnoreDecryptionErrors { - return errors.Wrap(ErrDecryptionFailed, err.Error()) + return decrypted.BodyErr } - return buildPGPMIMEFallbackRFC822(msg, opts, buf) + return buildPGPMIMEFallbackRFC822(decrypted, opts, buf) } - hdr := getMessageHeader(msg, opts) + hdr := getMessageHeader(decrypted.Msg, opts) - sigs, err := proton.ExtractSignatures(kr, msg.Body) + sigs, err := proton.ExtractSignatures(kr, decrypted.Msg.Body) if err != nil { - log.WithError(err).WithField("id", msg.ID).Warn("Extract signature failed") + log.WithError(err).WithField("id", decrypted.Msg.ID).Warn("Extract signature failed") } if len(sigs) > 0 { - return writeMultipartSignedRFC822(hdr, decrypted.Bytes(), sigs[0], buf) + return writeMultipartSignedRFC822(hdr, decrypted.Body.Bytes(), sigs[0], buf) } - return writeMultipartEncryptedRFC822(hdr, decrypted.Bytes(), buf) + return writeMultipartEncryptedRFC822(hdr, decrypted.Body.Bytes(), buf) } -func buildPGPMIMEFallbackRFC822(msg proton.Message, opts JobOptions, buf *bytes.Buffer) error { - hdr := getMessageHeader(msg, opts) +func buildPGPMIMEFallbackRFC822(decrypted *DecryptedMessage, opts JobOptions, buf *bytes.Buffer) error { + hdr := getMessageHeader(decrypted.Msg, opts) hdr.SetContentType("multipart/encrypted", map[string]string{ - "boundary": newBoundary(msg.ID).gen(), + "boundary": newBoundary(decrypted.Msg.ID).gen(), "protocol": "application/pgp-encrypted", }) @@ -307,7 +252,7 @@ func buildPGPMIMEFallbackRFC822(msg proton.Message, opts JobOptions, buf *bytes. dataHdr.SetContentDisposition("inline", map[string]string{"filename": "encrypted.asc"}) dataHdr.Set("Content-Description", "OpenPGP encrypted message") - if err := writePart(w, dataHdr, []byte(msg.Body)); err != nil { + if err := writePart(w, dataHdr, []byte(decrypted.Msg.Body)); err != nil { return err } diff --git a/pkg/message/build_custom.go b/pkg/message/build_custom.go index 175991da..dbbb7fc4 100644 --- a/pkg/message/build_custom.go +++ b/pkg/message/build_custom.go @@ -30,10 +30,10 @@ import ( // writeCustomTextPart writes an armored-PGP text part for a message body that couldn't be decrypted. func writeCustomTextPart( w *message.Writer, - msg proton.Message, + decrypted *DecryptedMessage, decError error, ) error { - enc, err := crypto.NewPGPMessageFromArmored(msg.Body) + enc, err := crypto.NewPGPMessageFromArmored(decrypted.Msg.Body) if err != nil { return err } @@ -48,7 +48,7 @@ func writeCustomTextPart( var hdr message.Header - hdr.SetContentType(string(msg.MIMEType), nil) + hdr.SetContentType(string(decrypted.Msg.MIMEType), nil) part, err := w.CreatePart(hdr) if err != nil { diff --git a/pkg/message/build_test.go b/pkg/message/build_test.go index 58c2ee9b..63807012 100644 --- a/pkg/message/build_test.go +++ b/pkg/message/build_test.go @@ -39,7 +39,7 @@ func TestBuildPlainMessage(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "text/plain", "body", time.Now()) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -56,7 +56,7 @@ func TestBuildPlainMessageWithLongKey(t *testing.T) { msg := newTestMessage(t, kr, "messageID", "addressID", "text/plain", "body", time.Now()) msg.ParsedHeaders["ReallyVeryVeryVeryVeryVeryLongLongLongLongLongLongLongKeyThatWillHaveNotSoLongValue"] = []string{"value"} - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -73,7 +73,7 @@ func TestBuildHTMLMessage(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "text/html", "body", time.Now()) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -91,7 +91,7 @@ func TestBuildPlainEncryptedMessage(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -116,7 +116,7 @@ func TestBuildPlainEncryptedMessageMissingHeader(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Now()) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -133,7 +133,7 @@ func TestBuildPlainEncryptedMessageInvalidHeader(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Now()) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -158,7 +158,7 @@ func TestBuildPlainSignedEncryptedMessageMissingHeader(t *testing.T) { msg := newRawTestMessage("messageID", "addressID", "multipart/mixed", arm, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -195,7 +195,7 @@ func TestBuildPlainSignedEncryptedMessageInvalidHeader(t *testing.T) { msg := newRawTestMessage("messageID", "addressID", "multipart/mixed", arm, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -224,7 +224,7 @@ func TestBuildPlainEncryptedLatin2Message(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -246,7 +246,7 @@ func TestBuildHTMLEncryptedMessage(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -280,7 +280,7 @@ func TestBuildPlainSignedMessage(t *testing.T) { msg := newRawTestMessage("messageID", "addressID", "multipart/mixed", arm, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -318,7 +318,7 @@ func TestBuildPlainSignedBase64Message(t *testing.T) { msg := newRawTestMessage("messageID", "addressID", "multipart/mixed", arm, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -349,7 +349,7 @@ func TestBuildSignedPlainEncryptedMessage(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -386,7 +386,7 @@ func TestBuildSignedHTMLEncryptedMessage(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -425,7 +425,7 @@ func TestBuildSignedPlainEncryptedMessageWithPubKey(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -471,7 +471,7 @@ func TestBuildSignedHTMLEncryptedMessageWithPubKey(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -518,7 +518,7 @@ func TestBuildSignedMultipartAlternativeEncryptedMessageWithPubKey(t *testing.T) kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -575,7 +575,7 @@ func TestBuildSignedEmbeddedMessageRFC822EncryptedMessageWithPubKey(t *testing.T kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -625,7 +625,7 @@ func TestBuildHTMLMessageWithAttachment(t *testing.T) { msg := newTestMessage(t, kr, "messageID", "addressID", "text/html", "body", time.Now()) att := addTestAttachment(t, kr, &msg, "attachID", "file.png", "image/png", "attachment", "attachment") - res, err := BuildRFC822(kr, msg, [][]byte{att}, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{att}, JobOptions{}) require.NoError(t, err) section(t, res, 1). @@ -649,7 +649,7 @@ func TestBuildHTMLMessageWithRFC822Attachment(t *testing.T) { msg := newTestMessage(t, kr, "messageID", "addressID", "text/html", "body", time.Now()) att := addTestAttachment(t, kr, &msg, "attachID", "file.eml", "message/rfc822", "attachment", "... message/rfc822 ...") - res, err := BuildRFC822(kr, msg, [][]byte{att}, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{att}, JobOptions{}) require.NoError(t, err) section(t, res, 1). @@ -673,7 +673,7 @@ func TestBuildHTMLMessageWithInlineAttachment(t *testing.T) { msg := newTestMessage(t, kr, "messageID", "addressID", "text/html", "body", time.Now()) inl := addTestAttachment(t, kr, &msg, "inlineID", "file.png", "image/png", "inline", "inline") - res, err := BuildRFC822(kr, msg, [][]byte{inl}, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{inl}, JobOptions{}) require.NoError(t, err) section(t, res, 1). @@ -703,7 +703,7 @@ func TestBuildHTMLMessageWithComplexAttachments(t *testing.T) { att0 := addTestAttachment(t, kr, &msg, "attachID0", "attach0.png", "image/png", "attachment", "attach0") att1 := addTestAttachment(t, kr, &msg, "attachID1", "attach1.png", "image/png", "attachment", "attach1") - res, err := BuildRFC822(kr, msg, [][]byte{ + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{ inl0, inl1, att0, @@ -756,7 +756,7 @@ func TestBuildAttachmentWithExoticFilename(t *testing.T) { msg := newTestMessage(t, kr, "messageID", "addressID", "text/html", "body", time.Now()) att := addTestAttachment(t, kr, &msg, "attachID", `I řeally šhould leařn czech.png`, "image/png", "attachment", "attachment") - res, err := BuildRFC822(kr, msg, [][]byte{att}, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{att}, JobOptions{}) require.NoError(t, err) // The "name" and "filename" params should actually be RFC2047-encoded because they aren't 7-bit clean. @@ -778,7 +778,7 @@ func TestBuildAttachmentWithLongFilename(t *testing.T) { msg := newTestMessage(t, kr, "messageID", "addressID", "text/html", "body", time.Now()) att := addTestAttachment(t, kr, &msg, "attachID", veryLongName, "image/png", "attachment", "attachment") - res, err := BuildRFC822(kr, msg, [][]byte{att}, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{att}, JobOptions{}) require.NoError(t, err) // NOTE: hasMaxLineLength is too high! Long filenames should be linewrapped using multipart filenames. @@ -798,7 +798,7 @@ func TestBuildMessageDate(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "text/plain", "body", time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res).expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`)) @@ -814,7 +814,7 @@ func TestBuildMessageWithInvalidDate(t *testing.T) { msg := newTestMessage(t, kr, "messageID", "addressID", "text/html", "body", time.Unix(-1, 0)) // Build the message as usual; the date will be before 1970. - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -822,7 +822,7 @@ func TestBuildMessageWithInvalidDate(t *testing.T) { expectHeader(`X-Original-Date`, isMissing()) // Build the message with date sanitization enabled; the date will be RFC822's birthdate. - resFix, err := BuildRFC822(kr, msg, nil, JobOptions{SanitizeDate: true}) + resFix, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{SanitizeDate: true}) require.NoError(t, err) section(t, resFix). @@ -849,7 +849,7 @@ func TestBuildMessageWithExistingOriginalDate(t *testing.T) { }) // Build the message as usual; the date will be before 1970. - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -857,7 +857,7 @@ func TestBuildMessageWithExistingOriginalDate(t *testing.T) { expectHeader(`X-Original-Date`, is("Sun, 15 Jan 2023 04:23:03 +0100 (W. Europe Standard Time)")) // Build the message with date sanitization enabled; the date will be RFC822's birthdate. - resFix, err := BuildRFC822(kr, msg, nil, JobOptions{SanitizeDate: true}) + resFix, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{SanitizeDate: true}) require.NoError(t, err) section(t, resFix). @@ -872,7 +872,7 @@ func TestBuildMessageInternalID(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "text/plain", "body", time.Now()) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res).expectHeader(`Message-Id`, is(``)) @@ -888,7 +888,7 @@ func TestBuildMessageExternalID(t *testing.T) { // Set the message's external ID; this should be used preferentially to set the Message-Id header field. msg.ExternalID = "externalID" - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res).expectHeader(`Message-Id`, is(``)) @@ -903,7 +903,7 @@ func TestBuild8BitBody(t *testing.T) { // Set an 8-bit body; the charset should be set to UTF-8. msg := newTestMessage(t, kr, "messageID", "addressID", "text/plain", "I řeally šhould leařn czech", time.Now()) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res).expectContentTypeParam(`charset`, is(`utf-8`)) @@ -919,7 +919,7 @@ func TestBuild8BitSubject(t *testing.T) { // Set an 8-bit subject; it should be RFC2047-encoded. msg.Subject = `I řeally šhould leařn czech` - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -940,7 +940,7 @@ func TestBuild8BitSender(t *testing.T) { Address: `mail@example.com`, } - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -961,7 +961,7 @@ func TestBuild8BitRecipients(t *testing.T) { {Name: `leařn czech`, Address: `mail2@example.com`}, } - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -979,12 +979,12 @@ func TestBuildIncludeMessageIDReference(t *testing.T) { // Add references. msg.ParsedHeaders["References"] = []string{""} - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res).expectHeader(`References`, is(``)) - resRef, err := BuildRFC822(kr, msg, nil, JobOptions{AddMessageIDReference: true}) + resRef, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{AddMessageIDReference: true}) require.NoError(t, err) section(t, resRef).expectHeader(`References`, is(` `)) @@ -999,10 +999,10 @@ func TestBuildMessageIsDeterministic(t *testing.T) { inl := addTestAttachment(t, kr, &msg, "inlineID", "file.png", "image/png", "inline", "inline") att := addTestAttachment(t, kr, &msg, "attachID", "attach.png", "image/png", "attachment", "attachment") - res1, err := BuildRFC822(kr, msg, [][]byte{inl, att}, JobOptions{}) + res1, err := DecryptAndBuildRFC822(kr, msg, [][]byte{inl, att}, JobOptions{}) require.NoError(t, err) - res2, err := BuildRFC822(kr, msg, [][]byte{inl, att}, JobOptions{}) + res2, err := DecryptAndBuildRFC822(kr, msg, [][]byte{inl, att}, JobOptions{}) require.NoError(t, err) assert.Equal(t, res1, res2) @@ -1017,7 +1017,7 @@ func TestBuildUndecryptableMessage(t *testing.T) { // Use a different keyring for encrypting the message; it won't be decryptable. msg := newTestMessage(t, utils.MakeKeyRing(t), "messageID", "addressID", "text/plain", "body", time.Now()) - _, err := BuildRFC822(kr, msg, nil, JobOptions{}) + _, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.ErrorIs(t, err, ErrDecryptionFailed) } @@ -1031,7 +1031,7 @@ func TestBuildUndecryptableAttachment(t *testing.T) { // Use a different keyring for encrypting the attachment; it won't be decryptable. att := addTestAttachment(t, utils.MakeKeyRing(t), &msg, "attachID", "file.png", "image/png", "attachment", "attachment") - _, err := BuildRFC822(kr, msg, [][]byte{att}, JobOptions{}) + _, err := DecryptAndBuildRFC822(kr, msg, [][]byte{att}, JobOptions{}) require.ErrorIs(t, err, ErrDecryptionFailed) } @@ -1046,7 +1046,7 @@ func TestBuildCustomMessagePlain(t *testing.T) { msg := newTestMessage(t, foreignKR, "messageID", "addressID", "text/plain", "body", time.Now()) // Tell the job to ignore decryption errors; a custom message will be returned instead of an error. - res, err := BuildRFC822(kr, msg, nil, JobOptions{IgnoreDecryptionErrors: true}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{IgnoreDecryptionErrors: true}) require.NoError(t, err) section(t, res). @@ -1070,7 +1070,7 @@ func TestBuildCustomMessageHTML(t *testing.T) { msg := newTestMessage(t, foreignKR, "messageID", "addressID", "text/html", "body", time.Now()) // Tell the job to ignore decryption errors; a custom message will be returned instead of an error. - res, err := BuildRFC822(kr, msg, nil, JobOptions{IgnoreDecryptionErrors: true}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{IgnoreDecryptionErrors: true}) require.NoError(t, err) section(t, res). @@ -1098,7 +1098,7 @@ func TestBuildCustomMessageEncrypted(t *testing.T) { msg.Subject = "this is a subject to make sure we preserve subject" // Tell the job to ignore decryption errors; a custom message will be returned instead of an error. - res, err := BuildRFC822(kr, msg, nil, JobOptions{IgnoreDecryptionErrors: true}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{IgnoreDecryptionErrors: true}) require.NoError(t, err) section(t, res). @@ -1132,7 +1132,7 @@ func TestBuildCustomMessagePlainWithAttachment(t *testing.T) { att := addTestAttachment(t, foreignKR, &msg, "attachID", "file.png", "image/png", "attachment", "attachment") // Tell the job to ignore decryption errors; a custom message will be returned instead of an error. - res, err := BuildRFC822(kr, msg, [][]byte{att}, JobOptions{IgnoreDecryptionErrors: true}) + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{att}, JobOptions{IgnoreDecryptionErrors: true}) require.NoError(t, err) section(t, res). @@ -1165,7 +1165,7 @@ func TestBuildCustomMessageHTMLWithAttachment(t *testing.T) { att := addTestAttachment(t, foreignKR, &msg, "attachID", "file.png", "image/png", "attachment", "attachment") // Tell the job to ignore decryption errors; a custom message will be returned instead of an error. - res, err := BuildRFC822(kr, msg, [][]byte{att}, JobOptions{IgnoreDecryptionErrors: true}) + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{att}, JobOptions{IgnoreDecryptionErrors: true}) require.NoError(t, err) section(t, res). @@ -1200,7 +1200,7 @@ func TestBuildCustomMessageOnlyBodyIsUndecryptable(t *testing.T) { att := addTestAttachment(t, kr, &msg, "attachID", "file.png", "image/png", "attachment", "attachment") // Tell the job to ignore decryption errors; a custom message will be returned instead of an error. - res, err := BuildRFC822(kr, msg, [][]byte{att}, JobOptions{IgnoreDecryptionErrors: true}) + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{att}, JobOptions{IgnoreDecryptionErrors: true}) require.NoError(t, err) section(t, res). @@ -1233,7 +1233,7 @@ func TestBuildCustomMessageOnlyAttachmentIsUndecryptable(t *testing.T) { att := addTestAttachment(t, foreignKR, &msg, "attachID", "file.png", "image/png", "attachment", "attachment") // Tell the job to ignore decryption errors; a custom message will be returned instead of an error. - res, err := BuildRFC822(kr, msg, [][]byte{att}, JobOptions{IgnoreDecryptionErrors: true}) + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{att}, JobOptions{IgnoreDecryptionErrors: true}) require.NoError(t, err) section(t, res). @@ -1271,7 +1271,7 @@ func TestBuildComplexMIMEType(t *testing.T) { att0 := addTestAttachment(t, kr, &msg, "attachID0", "attach0.png", "image/png", "attachment", "attach0") att1 := addTestAttachment(t, kr, &msg, "attachID1", "Cat_August_2010-4.jpeg", "image/jpeg; name=Cat_August_2010-4.jpeg; x-unix-mode=0644", "attachment", "attach1") - res, err := BuildRFC822(kr, msg, [][]byte{ + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{ att0, att1, }, JobOptions{}) diff --git a/pkg/message/decrypt.go b/pkg/message/decrypt.go new file mode 100644 index 00000000..ee6a7dd2 --- /dev/null +++ b/pkg/message/decrypt.go @@ -0,0 +1,88 @@ +// Copyright (c) 2023 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 . + +package message + +import ( + "bytes" + "encoding/base64" + "io" + + "github.com/ProtonMail/go-proton-api" + "github.com/ProtonMail/gopenpgp/v2/crypto" + "github.com/pkg/errors" +) + +type DecryptedAttachment struct { + Packet []byte + Encrypted []byte + Data bytes.Buffer + Err error +} + +type DecryptedMessage struct { + Msg proton.Message + Body bytes.Buffer + BodyErr error + Attachments []DecryptedAttachment +} + +var ErrInvalidAttachmentPacket = errors.New("invalid attachment packet") + +func DecryptMessage(kr *crypto.KeyRing, msg proton.Message, attData [][]byte) DecryptedMessage { + result := DecryptedMessage{ + Msg: msg, + } + + result.Body.Grow(len(msg.Body)) + + if err := msg.DecryptInto(kr, &result.Body); err != nil { + result.BodyErr = errors.Wrap(ErrDecryptionFailed, err.Error()) + } + + result.Attachments = make([]DecryptedAttachment, len(msg.Attachments)) + + for i, attachment := range msg.Attachments { + result.Attachments[i].Encrypted = attData[i] + + kps, err := base64.StdEncoding.DecodeString(attachment.KeyPackets) + if err != nil { + result.Attachments[i].Err = errors.Wrap(ErrInvalidAttachmentPacket, err.Error()) + continue + } + + result.Attachments[i].Packet = kps + + // Use io.Multi + attachmentReader := io.MultiReader(bytes.NewReader(kps), bytes.NewReader(attData[i])) + + stream, err := kr.DecryptStream(attachmentReader, nil, crypto.GetUnixTime()) + if err != nil { + result.Attachments[i].Err = errors.Wrap(ErrDecryptionFailed, err.Error()) + continue + } + + result.Attachments[i].Data.Grow(len(kps) + len(attData)) + + if _, err := result.Attachments[i].Data.ReadFrom(stream); err != nil { + result.Attachments[i].Err = errors.Wrap(ErrDecryptionFailed, err.Error()) + continue + } + } + + return result +} diff --git a/pkg/message/decrypt_and_build.go b/pkg/message/decrypt_and_build.go new file mode 100644 index 00000000..9c35678c --- /dev/null +++ b/pkg/message/decrypt_and_build.go @@ -0,0 +1,40 @@ +// Copyright (c) 2023 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 . + +package message + +import ( + "bytes" + + "github.com/ProtonMail/go-proton-api" + "github.com/ProtonMail/gopenpgp/v2/crypto" +) + +func DecryptAndBuildRFC822(kr *crypto.KeyRing, msg proton.Message, attData [][]byte, opts JobOptions) ([]byte, error) { + buf := new(bytes.Buffer) + if err := DecryptAndBuildRFC822Into(kr, msg, attData, opts, buf); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func DecryptAndBuildRFC822Into(kr *crypto.KeyRing, msg proton.Message, attData [][]byte, opts JobOptions, buf *bytes.Buffer) error { + decrypted := DecryptMessage(kr, msg, attData) + + return BuildRFC822Into(kr, &decrypted, opts, buf) +} From 87e79fdcba77f4c0ed30bd37ed0ef329c780e59d Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Thu, 20 Jul 2023 12:32:03 +0200 Subject: [PATCH 07/93] feat(GODT-2770): proof of concept for web view as overlay. --- .../bridge-gui/bridge-gui/CMakeLists.txt | 3 +- .../bridge-gui/bridge-gui/Resources.qrc | 3 +- .../frontend/bridge-gui/bridge-gui/main.cpp | 2 + .../bridge-gui/bridge-gui/qml/HelpView.qml | 2 +- .../bridge-gui/bridge-gui/qml/MainWindow.qml | 13 +++- .../bridge-gui/qml/Proton/Style.qml | 10 +++ .../bridge-gui/qml/Proton/WebViewOverlay.qml | 74 +++++++++++++++++++ .../bridge-gui/bridge-gui/qml/Proton/qmldir | 1 + 8 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebViewOverlay.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/CMakeLists.txt b/internal/frontend/bridge-gui/bridge-gui/CMakeLists.txt index 2c14ed83..84bca59b 100644 --- a/internal/frontend/bridge-gui/bridge-gui/CMakeLists.txt +++ b/internal/frontend/bridge-gui/bridge-gui/CMakeLists.txt @@ -75,7 +75,7 @@ if(NOT UNIX) set(CMAKE_INSTALL_BINDIR ".") endif(NOT UNIX) -find_package(Qt6 COMPONENTS Core Quick Qml QuickControls2 Widgets Svg REQUIRED) +find_package(Qt6 COMPONENTS Core Quick Qml QuickControls2 Widgets Svg WebView REQUIRED) qt_standard_project_setup() set(CMAKE_AUTORCC ON) message(STATUS "Using Qt ${Qt6_VERSION}") @@ -148,6 +148,7 @@ target_link_libraries(bridge-gui Qt6::Qml Qt6::QuickControls2 Qt6::Svg + Qt6::WebView sentry::sentry bridgepp ) diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index 8eed89d2..d6164b36 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -100,8 +100,9 @@ qml/Proton/TextArea.qml qml/Proton/TextField.qml qml/Proton/Toggle.qml + qml/Proton/WebViewOverlay.qml qml/QuestionItem.qml - qml/Resources/bug_report_flow.json + qml/Resources/bug_report_flow.json qml/SettingsItem.qml qml/SettingsView.qml qml/SetupGuide.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/main.cpp b/internal/frontend/bridge-gui/bridge-gui/main.cpp index dc8eca14..6da1e70e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/main.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/main.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #ifdef Q_OS_MACOS @@ -284,6 +285,7 @@ int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL); // must be called before instantiating the BridgeApp } + QtWebView::initialize(); BridgeApp guiApp(argc, argv); initSentry(); auto sentryCloser = qScopeGuard([] { sentry_close(); }); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml b/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml index 546f947d..58b64831 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml @@ -37,7 +37,7 @@ SettingsView { onClicked: { Backend.notifyKBArticleClicked("https://proton.me/support/bridge"); - Qt.openUrlExternally("https://proton.me/support/bridge"); + Backend.showHelp() } } SettingsItem { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index a16ebc52..c5b76877 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -36,7 +36,7 @@ ApplicationWindow { } } function showHelp() { - contentWrapper.showHelp(); + showWebViewOverlay("https://proton.me/support/bridge"); } function showLocalCacheSettings() { contentWrapper.showLocalCacheSettings(); @@ -55,6 +55,10 @@ ApplicationWindow { return; contentWrapper.showSignIn(username); } + function showWebViewOverlay(url) { + webViewOverlay.visible = true; + webViewOverlay.url = url; + } colorScheme: ProtonStyle.currentStyle height: _defaultHeight @@ -196,4 +200,11 @@ ApplicationWindow { id: splashScreen colorScheme: root.colorScheme } + WebViewOverlay { + id: webViewOverlay + anchors.fill: parent + colorScheme: root.colorScheme + url: "" + visible: false + } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml index 3e6379a3..86a44821 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml @@ -367,4 +367,14 @@ QtObject { property int title_font_size: 20 property int title_line_height: 24 property real tooltip_radius: 8 * root.px // px + + // WebView overlay styling + property real web_view_button_width: 320 * root.px + property real web_view_corner_radius: 10 * root.px + property real web_view_overlay_horizontal_margin: 10 * root.px + property real web_view_overlay_vertical_margin: web_view_corner_radius + property real web_view_overlay_opacity: 0.6 + property real web_view_overlay_button_vertical_margin: 10 * root.px + property real web_view_overlay_margin: 50 * root.px + property real web_view_overley_border_width: 1 * root.px } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebViewOverlay.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebViewOverlay.qml new file mode 100644 index 00000000..7936f59b --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebViewOverlay.qml @@ -0,0 +1,74 @@ +// Copyright (c) 2023 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 . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.impl +import QtWebView +import QtQuick.Templates as T +import "." as Proton + +Item { + id: root + + property ColorScheme colorScheme + property url url + + Rectangle { + anchors.fill: parent + color: "#000" + opacity: ProtonStyle.web_view_overlay_opacity + } + Rectangle { + anchors.fill: parent + anchors.margins: ProtonStyle.web_view_overlay_margin + color: root.colorScheme.background_norm + radius: ProtonStyle.web_view_corner_radius + + ColumnLayout { + anchors.bottomMargin: 0 + anchors.fill: parent + anchors.leftMargin: ProtonStyle.web_view_overlay_horizontal_margin + anchors.rightMargin: ProtonStyle.web_view_overlay_horizontal_margin + anchors.topMargin: ProtonStyle.web_view_overlay_vertical_margin + spacing: 0 + + Rectangle { + Layout.fillHeight: true + Layout.fillWidth: true + border.color: root.colorScheme.border_norm + border.width: ProtonStyle.web_view_overley_border_width + + WebView { + anchors.fill: parent + anchors.margins: ProtonStyle.web_view_overley_border_width + url: root.url + } + } + Button { + Layout.alignment: Qt.AlignCenter + Layout.bottomMargin: ProtonStyle.web_view_overlay_button_vertical_margin + Layout.preferredWidth: ProtonStyle.web_view_button_width + Layout.topMargin: ProtonStyle.web_view_overlay_button_vertical_margin + colorScheme: root.colorScheme + text: qsTr("Close") + + onClicked: { + root.url = ""; + root.visible = false; + } + } + } + } +} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir index b8750786..1b07d385 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir @@ -36,3 +36,4 @@ Switch 4.0 Switch.qml TextArea 4.0 TextArea.qml TextField 4.0 TextField.qml Toggle 4.0 Toggle.qml +WebViewOverlay 4.0 WebViewOverlay.qml From 7b96a07cf52497a354575162ada96159cb9640d8 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Thu, 20 Jul 2023 18:16:44 +0200 Subject: [PATCH 08/93] feat(GODT-2770): proof of concept for web view as a tool window. --- .../bridge-gui/bridge-gui/QMLBackend.h | 1 + .../bridge-gui/bridge-gui/Resources.qrc | 2 ++ .../bridge-gui/bridge-gui/qml/Bridge.qml | 15 ++++++++++ .../bridge-gui/bridge-gui/qml/HelpView.qml | 2 +- .../bridge-gui/bridge-gui/qml/MainWindow.qml | 17 ++++++----- .../{WebViewOverlay.qml => WebView.qml} | 21 ++++++++----- .../bridge-gui/bridge-gui/qml/Proton/qmldir | 2 +- .../bridge-gui/qml/WebViewWindow.qml | 30 +++++++++++++++++++ 8 files changed, 74 insertions(+), 16 deletions(-) rename internal/frontend/bridge-gui/bridge-gui/qml/Proton/{WebViewOverlay.qml => WebView.qml} (75%) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/WebViewWindow.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h index f16e9c59..0f086f7e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h @@ -272,6 +272,7 @@ signals: // Signals received from the Go backend, to be forwarded to QML void hideMainWindow(); ///< Signal for the 'hideMainWindow' gRPC stream event. void showHelp(); ///< Signal for the 'showHelp' event (from the context menu). void showSettings(); ///< Signal for the 'showHelp' event (from the context menu). + void showWebViewWindow(QString const &url); ///< Signal the the 'showWebViewWindow' event void selectUser(QString const& userID, bool forceShowWindow); ///< Signal emitted in order to selected a user with a given ID in the list. void genericError(QString const &title, QString const &description); ///< Signal for the 'genericError' gRPC stream event. void imapLoginWhileSignedOut(QString const& username); ///< Signal for the notification of IMAP login attempt on a signed out account. diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index d6164b36..0016eff5 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -100,6 +100,7 @@ qml/Proton/TextArea.qml qml/Proton/TextField.qml qml/Proton/Toggle.qml + qml/Proton/WebView.qml qml/Proton/WebViewOverlay.qml qml/QuestionItem.qml qml/Resources/bug_report_flow.json @@ -111,5 +112,6 @@ qml/SplashScreen.qml qml/Status.qml qml/WelcomeGuide.qml + qml/WebViewWindow.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml index 1f1a6c4c..8ee7c5e8 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml @@ -69,6 +69,21 @@ QtObject { Backend.setNormalTrayIcon(); } } + property WebViewWindow _webviewWindow: WebViewWindow { + id: webViewWindow + flags: Qt.Tool + transientParent: mainWindow + visible: false + + Connections { + function onShowWebViewWindow(url) { + webViewWindow.url = url; + webViewWindow.show(); + } + + target: Backend + } + } property var title: Backend.appname function bound(num, lowerLimit, upperLimit) { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml b/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml index 58b64831..4a604989 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml @@ -104,7 +104,7 @@ SettingsView { type: Label.Caption onLinkActivated: function (link) { - Qt.openUrlExternally(link); + Backend.showWebViewWindow(link) } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index c5b76877..3e53999a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -191,6 +191,16 @@ ApplicationWindow { } } } + + WebView { + id: webViewOverlay + anchors.fill: parent + colorScheme: root.colorScheme + overlay: true + url: "" + visible: false + } + NotificationPopups { colorScheme: root.colorScheme mainWindow: root @@ -200,11 +210,4 @@ ApplicationWindow { id: splashScreen colorScheme: root.colorScheme } - WebViewOverlay { - id: webViewOverlay - anchors.fill: parent - colorScheme: root.colorScheme - url: "" - visible: false - } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebViewOverlay.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebView.qml similarity index 75% rename from internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebViewOverlay.qml rename to internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebView.qml index 7936f59b..dc064edd 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebViewOverlay.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebView.qml @@ -16,41 +16,47 @@ import QtQuick.Layouts import QtQuick.Controls import QtQuick.Controls.impl import QtWebView -import QtQuick.Templates as T import "." as Proton Item { id: root property ColorScheme colorScheme - property url url + property bool overlay: true + property string url: "" + + function showBlankPage() { + webView.loadHtml("blank", "blank.html"); + } Rectangle { anchors.fill: parent color: "#000" opacity: ProtonStyle.web_view_overlay_opacity + visible: overlay } Rectangle { anchors.fill: parent - anchors.margins: ProtonStyle.web_view_overlay_margin + anchors.margins: overlay ? ProtonStyle.web_view_overlay_margin : 0 color: root.colorScheme.background_norm radius: ProtonStyle.web_view_corner_radius ColumnLayout { anchors.bottomMargin: 0 anchors.fill: parent - anchors.leftMargin: ProtonStyle.web_view_overlay_horizontal_margin - anchors.rightMargin: ProtonStyle.web_view_overlay_horizontal_margin - anchors.topMargin: ProtonStyle.web_view_overlay_vertical_margin + anchors.leftMargin: overlay ? ProtonStyle.web_view_overlay_horizontal_margin : 0 + anchors.rightMargin: overlay ? ProtonStyle.web_view_overlay_horizontal_margin : 0 + anchors.topMargin: overlay ? ProtonStyle.web_view_overlay_vertical_margin : 0 spacing: 0 Rectangle { Layout.fillHeight: true Layout.fillWidth: true border.color: root.colorScheme.border_norm - border.width: ProtonStyle.web_view_overley_border_width + border.width: overlay ? ProtonStyle.web_view_overley_border_width : 0 WebView { + id: webView anchors.fill: parent anchors.margins: ProtonStyle.web_view_overley_border_width url: root.url @@ -63,6 +69,7 @@ Item { Layout.topMargin: ProtonStyle.web_view_overlay_button_vertical_margin colorScheme: root.colorScheme text: qsTr("Close") + visible: overlay onClicked: { root.url = ""; diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir index 1b07d385..4e6ccf3a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir @@ -36,4 +36,4 @@ Switch 4.0 Switch.qml TextArea 4.0 TextArea.qml TextField 4.0 TextField.qml Toggle 4.0 Toggle.qml -WebViewOverlay 4.0 WebViewOverlay.qml +WebView 4.0 WebView.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/WebViewWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/WebViewWindow.qml new file mode 100644 index 00000000..12ecd3c3 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/WebViewWindow.qml @@ -0,0 +1,30 @@ +// Copyright (c) 2023 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 . +import QtQml +import QtQuick +import QtQuick.Controls +import Proton + +Window { + id: root + height: 600 + width: 800 + minimumWidth: 600 + property string url + WebView { + anchors.fill: parent + colorScheme: ProtonStyle.currentStyle + overlay: false + url: root.url + } +} From e5bac33a04851a99373afba1af12cf3099a36704 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Mon, 24 Jul 2023 20:26:05 +0200 Subject: [PATCH 09/93] feat(GODT-2767): setup wizard frame. WIP [skip-cli] --- .../bridge-gui/bridge-gui/Resources.qrc | 3 + .../bridge-gui/qml/ContentWrapper.qml | 3 +- .../bridge-gui/bridge-gui/qml/MainWindow.qml | 15 ++- .../bridge-gui/qml/Proton/ColorScheme.qml | 1 + .../bridge-gui/qml/Proton/Style.qml | 10 +- .../qml/SetupWizard/SetupWizard.qml | 102 ++++++++++++++++++ .../bridge-gui/qml/bridgeqml.qmlproject | 37 ------- .../qml/icons/img-mail-logo-wordmark-dark.svg | 25 +++++ .../qml/icons/img-mail-logo-wordmark.svg | 25 +++++ 9 files changed, 177 insertions(+), 44 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml delete mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/bridgeqml.qmlproject create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark-dark.svg create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark.svg diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index 0016eff5..d3eca851 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -49,6 +49,8 @@ qml/icons/ic-success.svg qml/icons/ic-three-dots-vertical.svg qml/icons/ic-trash.svg + qml/icons/img-mail-logo-wordmark-dark.svg + qml/icons/img-mail-logo-wordmark.svg qml/icons/img-proton-logos.png qml/icons/img-proton-logos.svg qml/icons/img-splash.png @@ -107,6 +109,7 @@ qml/SettingsItem.qml qml/SettingsView.qml qml/SetupGuide.qml + qml/SetupWizard/SetupWizard.qml qml/SignIn.qml qml/ConnectionModeSettings.qml qml/SplashScreen.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml b/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml index b5224262..55e8ee51 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml @@ -25,6 +25,7 @@ Item { signal closeWindow signal quitBridge signal showSetupGuide(var user, string address) + signal showSetupWizard function selectUser(userID) { const users = Backend.users; @@ -283,7 +284,7 @@ Item { onClicked: { signIn.username = ""; - rightContent.showSignIn(); + root.showSetupWizard(); } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index 3e53999a..819b3dd7 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -17,6 +17,7 @@ import QtQuick.Layouts import QtQuick.Controls import Proton import Notifications +import "SetupWizard" ApplicationWindow { id: root @@ -129,7 +130,8 @@ ApplicationWindow { currentIndex: { // show welcome when there are no users if (Backend.users.count === 0) { - return 1; + setupWizard.start(); + return 0; } const u = Backend.users.get(0); if (!u) { @@ -167,6 +169,9 @@ ApplicationWindow { onShowSetupGuide: function (user, address) { root.showSetup(user, address); } + onShowSetupWizard: { + setupWizard.start(); + } } WelcomeGuide { Layout.fillHeight: true @@ -191,7 +196,6 @@ ApplicationWindow { } } } - WebView { id: webViewOverlay anchors.fill: parent @@ -200,7 +204,12 @@ ApplicationWindow { url: "" visible: false } - + SetupWizard { + id: setupWizard + anchors.fill: parent + colorScheme: root.colorScheme + visible: false + } NotificationPopups { colorScheme: root.colorScheme mainWindow: root diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ColorScheme.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ColorScheme.qml index d795b7f4..146d5cfd 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ColorScheme.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ColorScheme.qml @@ -48,6 +48,7 @@ QtObject { property color interaction_weak_active property color interaction_weak_hover property string logo_img + property string mail_logo_with_wordmark // Primary property color primary_norm diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml index 86a44821..77df2eae 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml @@ -72,6 +72,7 @@ QtObject { interaction_weak_active: "#6D697D" interaction_weak_hover: "#5B576B" logo_img: "/qml/icons/product_logos_dark.svg" + mail_logo_with_wordmark: "/qml/icons/img-mail-logo-wordmark-dark.svg" // Primary primary_norm: "#8A6EFF" @@ -145,6 +146,7 @@ QtObject { interaction_weak_active: "#6D697D" interaction_weak_hover: "#5B576B" logo_img: "/qml/icons/product_logos_dark.svg" + mail_logo_with_wordmark: "/qml/icons/img-mail-logo-wordmark-dark.svg" // Primary primary_norm: "#8A6EFF" @@ -245,6 +247,7 @@ QtObject { interaction_weak_active: "#8A6EFF" interaction_weak_hover: "#6D4AFF" logo_img: "/qml/icons/product_logos_dark.svg" + mail_logo_with_wordmark: "/qml/icons/img-mail-logo-wordmark-dark.svg" // Primary primary_norm: "#8A6EFF" @@ -325,6 +328,7 @@ QtObject { interaction_weak_active: "#A8A6A3" interaction_weak_hover: "#C2BFBC" logo_img: "/qml/icons/product_logos.svg" + mail_logo_with_wordmark: "/qml/icons/img-mail-logo-wordmark.svg" // Primary primary_norm: "#6D4AFF" @@ -371,10 +375,10 @@ QtObject { // WebView overlay styling property real web_view_button_width: 320 * root.px property real web_view_corner_radius: 10 * root.px - property real web_view_overlay_horizontal_margin: 10 * root.px - property real web_view_overlay_vertical_margin: web_view_corner_radius - property real web_view_overlay_opacity: 0.6 property real web_view_overlay_button_vertical_margin: 10 * root.px + property real web_view_overlay_horizontal_margin: 10 * root.px property real web_view_overlay_margin: 50 * root.px + property real web_view_overlay_opacity: 0.6 + property real web_view_overlay_vertical_margin: web_view_corner_radius property real web_view_overley_border_width: 1 * root.px } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml new file mode 100644 index 00000000..b1c49bdd --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -0,0 +1,102 @@ +// Copyright (c) 2023 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 . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.impl +import "." as Proton + +Item { + id: root + + property ColorScheme colorScheme + + function start() { + root.visible = true + } + + RowLayout { + anchors.fill: parent + spacing: 0 + + Rectangle { + id: leftHalf + Layout.fillHeight: true + Layout.fillWidth: true + color: root.colorScheme.background_norm + + Rectangle { + id: leftContent + anchors.bottom: parent.bottom + anchors.bottomMargin: 96 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 96 + color: "#ff0000" + width: 444 + } + Image { + id: mailLogoWithWordmark + anchors.bottom: parent.bottom + anchors.bottomMargin: 48 + anchors.horizontalCenter: parent.horizontalCenter + antialiasing: true + fillMode: Image.PreserveAspectFit + height: 24 + smooth: true + source: root.colorScheme.mail_logo_with_wordmark + sourceSize.height: 24 + sourceSize.width: 142 + } + } + Rectangle { + id: rightHalf + Layout.fillHeight: true + Layout.fillWidth: true + color: root.colorScheme.background_weak + + Rectangle { + id: rightContent + anchors.bottom: parent.bottom + anchors.bottomMargin: 96 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 96 + color: "#ff0000" + width: 444 + } + Label { + id: reportProblemLink + anchors.bottom: parent.bottom + anchors.bottomMargin: 48 + anchors.horizontalCenter: parent.horizontalCenter + colorScheme: root.colorScheme + horizontalAlignment: Text.AlignRight + text: link("#", "Report problem") + width: 444 + + onLinkActivated: { + root.visible = false; + } + + HoverHandler { + id: mouse + acceptedDevices: PointerDevice.Mouse + cursorShape: Qt.PointingHandCursor + } + } + } + } +} + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/bridgeqml.qmlproject b/internal/frontend/bridge-gui/bridge-gui/qml/bridgeqml.qmlproject deleted file mode 100644 index 15f0db8e..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/bridgeqml.qmlproject +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2022 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 . - -import QmlProject 1.1 - -Project { - mainFile: "./MainWindow.qml" - - /* Include .qml, .js, and image files from current directory and subdirectories */ - QmlFiles { - directory: "./" - } - JavaScriptFiles { - directory: "./" - } - ImageFiles { - directory: "./" - } - /* List of plugin directories passed to QML runtime */ - importPaths: [ - "./" - ] -} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark-dark.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark-dark.svg new file mode 100644 index 00000000..b1d4e727 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark-dark.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark.svg new file mode 100644 index 00000000..76b55db8 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + From 635b2a489185e5298fcb76ddeb5f9dae456c5fe7 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Wed, 26 Jul 2023 07:52:37 +0200 Subject: [PATCH 10/93] feat(GODT-2762): setup wizard: onboarding left pane. --- .../bridge-gui/bridge-gui/Resources.qrc | 4 +- .../bridge-gui/qml/Proton/ColorScheme.qml | 3 - .../bridge-gui/qml/Proton/Style.qml | 12 - .../qml/SetupWizard/OnboardingLeftPane.qml | 78 ++++ .../qml/SetupWizard/SetupWizard.qml | 9 +- .../bridge-gui/qml/WelcomeGuide.qml | 2 +- .../bridge-gui/qml/icons/img-welcome-dark.png | Bin 75665 -> 0 bytes .../bridge-gui/qml/icons/img-welcome-dark.svg | 331 ---------------- .../bridge-gui/qml/icons/img-welcome.png | Bin 74607 -> 0 bytes .../bridge-gui/qml/icons/img-welcome.svg | 361 ++---------------- 10 files changed, 119 insertions(+), 681 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/OnboardingLeftPane.qml delete mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome-dark.png delete mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome-dark.svg delete mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome.png diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index d3eca851..e7444c81 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -55,9 +55,6 @@ qml/icons/img-proton-logos.svg qml/icons/img-splash.png qml/icons/img-splash.svg - qml/icons/img-welcome-dark.png - qml/icons/img-welcome-dark.svg - qml/icons/img-welcome.png qml/icons/img-welcome.svg qml/icons/Loader_16.svg qml/icons/Loader_48.svg @@ -110,6 +107,7 @@ qml/SettingsView.qml qml/SetupGuide.qml qml/SetupWizard/SetupWizard.qml + qml/SetupWizard/OnboardingLeftPane.qml qml/SignIn.qml qml/ConnectionModeSettings.qml qml/SplashScreen.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ColorScheme.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ColorScheme.qml index 146d5cfd..b9f27354 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ColorScheme.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ColorScheme.qml @@ -83,7 +83,4 @@ QtObject { // Text property color text_norm property color text_weak - - // Images - property string welcome_img } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml index 77df2eae..bf1561ce 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml @@ -106,9 +106,6 @@ QtObject { // Text text_norm: "#FFFFFF" text_weak: "#A7A4B5" - - // Images - welcome_img: "/qml/icons/img-welcome-dark.png" } property ColorScheme darkStyle: ColorScheme { id: _darkStyle @@ -180,9 +177,6 @@ QtObject { // Text text_norm: "#FFFFFF" text_weak: "#A7A4B5" - - // Images - welcome_img: "/qml/icons/img-welcome-dark.png" } property real dialog_radius: 12 * root.px // px property int fontWeight_100: Font.Thin @@ -281,9 +275,6 @@ QtObject { // Text text_norm: "#FFFFFF" text_weak: "#9282D4" - - // Images - welcome_img: "/qml/icons/img-welcome-dark.png" } // TODO: Once we will use Qt >=5.15 this should be refactored with inline components as follows: // https://doc.qt.io/qt-5/qtqml-documents-definetypes.html#inline-components @@ -362,9 +353,6 @@ QtObject { // Text text_norm: "#0C0C14" text_weak: "#706D6B" - - // Images - welcome_img: "/qml/icons/img-welcome.png" } property real progress_bar_radius: 3 * root.px // px property real px: 1.00 // px diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/OnboardingLeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/OnboardingLeftPane.qml new file mode 100644 index 00000000..e8a6a538 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/OnboardingLeftPane.qml @@ -0,0 +1,78 @@ +// Copyright (c) 2023 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 . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.impl +import "." as Proton + +Item { + id: root + + property ColorScheme colorScheme + + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + spacing: 0 + + Image { + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + Layout.preferredHeight: 148 + Layout.preferredWidth: 265 + antialiasing: true + source: "/qml/icons/img-welcome.svg" + sourceSize.height: 148 + sourceSize.width: 265 + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Layout.topMargin: 16 + colorScheme: root.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Welcome to\nProton Mail Bridge") + type: Label.LabelType.Heading + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Layout.topMargin: 96 + colorScheme: root.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Bridge is the gateway between your Proton account and your email client. It runs in the background and encrypts and decrypts your messages seamlessly. ") + type: Label.LabelType.Body + wrapMode: Text.WordWrap + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: false + Layout.topMargin: 48 + colorScheme: root.colorScheme + horizontalAlignment: Text.AlignHCenter + text: link("https://proton.me/support/bridge", qsTr("Why do I need Bridge?")) + type: Label.LabelType.Body + + onLinkActivated: function (link) { + Qt.openUrlExternally(link); + } + + HoverHandler { + acceptedDevices: PointerDevice.Mouse + cursorShape: Qt.PointingHandCursor + } + } + } +} \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index b1c49bdd..03ce9e89 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -36,15 +36,20 @@ Item { Layout.fillWidth: true color: root.colorScheme.background_norm - Rectangle { + Item { id: leftContent anchors.bottom: parent.bottom anchors.bottomMargin: 96 anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: 96 - color: "#ff0000" width: 444 + + OnboardingLeftPane { + colorScheme: root.colorScheme + anchors.fill: parent + } + } Image { id: mailLogoWithWordmark diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/WelcomeGuide.qml b/internal/frontend/bridge-gui/bridge-gui/qml/WelcomeGuide.qml index 8be55925..94df5dec 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/WelcomeGuide.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/WelcomeGuide.qml @@ -95,7 +95,7 @@ Item { Image { Layout.alignment: Qt.AlignHCenter Layout.topMargin: 16 - source: colorScheme.welcome_img + source: "/qml/icons/img-welcome.svg" sourceSize.height: 148 sourceSize.width: 264 } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome-dark.png b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome-dark.png deleted file mode 100644 index d1b391654f755e17f9ce4dea50271a496a309e3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 75665 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 C3AJ|s diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome-dark.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome-dark.svg deleted file mode 100644 index 2e3e9272..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome-dark.svg +++ /dev/null @@ -1,331 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome.png b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome.png deleted file mode 100644 index 90fff1b99f3e13d0ef964e7de6bf86d9104c385f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74607 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 diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome.svg index dc6e069d..9bedcc34 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome.svg +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome.svg @@ -1,331 +1,34 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From cdc19492eeed594aaf7e72dd7028866b227d70e2 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Wed, 26 Jul 2023 17:03:54 +0200 Subject: [PATCH 11/93] feat(GODT-2762): onboarding right pane. --- .../bridge-gui/bridge-gui/Resources.qrc | 4 + .../qml/SetupWizard/OnboardingRightPane.qml | 61 ++++++ .../qml/SetupWizard/SetupWizard.qml | 13 +- .../qml/SetupWizard/StepDescriptionBox.qml | 68 +++++++ .../bridge-gui/qml/icons/ic-bridge.svg | 13 ++ .../qml/icons/ic-mozilla-thunderbird.svg | 190 ++++++++---------- .../bridge-gui/qml/icons/img-mail-clients.svg | 80 ++++++++ 7 files changed, 313 insertions(+), 116 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/OnboardingRightPane.qml create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-bridge.svg create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-clients.svg diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index e7444c81..488b8637 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -20,6 +20,7 @@ qml/icons/ic-alert.svg qml/icons/ic-apple-mail.svg qml/icons/ic-arrow-left.svg + qml/icons/ic-bridge.svg qml/icons/ic-card-identity.svg qml/icons/ic-check.svg qml/icons/ic-chevron-down.svg @@ -49,6 +50,7 @@ qml/icons/ic-success.svg qml/icons/ic-three-dots-vertical.svg qml/icons/ic-trash.svg + qml/icons/img-mail-clients.svg qml/icons/img-mail-logo-wordmark-dark.svg qml/icons/img-mail-logo-wordmark.svg qml/icons/img-proton-logos.png @@ -108,6 +110,8 @@ qml/SetupGuide.qml qml/SetupWizard/SetupWizard.qml qml/SetupWizard/OnboardingLeftPane.qml + qml/SetupWizard/OnboardingRightPane.qml + qml/SetupWizard/StepDescriptionBox.qml qml/SignIn.qml qml/ConnectionModeSettings.qml qml/SplashScreen.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/OnboardingRightPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/OnboardingRightPane.qml new file mode 100644 index 00000000..5a66de44 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/OnboardingRightPane.qml @@ -0,0 +1,61 @@ +// Copyright (c) 2023 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 . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.impl +import "." as Proton + +Item { + id: root + + property ColorScheme colorScheme + + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + spacing: 96 + + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: root.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Two-step process") + type: Label.LabelType.Heading + } + StepDescriptionBox { + colorScheme: root.colorScheme + description: "Connect Bridge to your Proton account" + icon: "/qml/icons/ic-bridge.svg" + title: "Step 1" + iconSize: 48 + } + StepDescriptionBox { + colorScheme: root.colorScheme + description: "Connect your email client to Bridge" + icon: "/qml/icons/img-mail-clients.svg" + title: "Step 2" + iconSize: 64 + + } + Button { + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: 320 + colorScheme: root.colorScheme + text: "Let's start" + } + } +} \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 03ce9e89..a142f6ff 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -23,7 +23,7 @@ Item { property ColorScheme colorScheme function start() { - root.visible = true + root.visible = true; } RowLayout { @@ -46,10 +46,9 @@ Item { width: 444 OnboardingLeftPane { - colorScheme: root.colorScheme anchors.fill: parent + colorScheme: root.colorScheme } - } Image { id: mailLogoWithWordmark @@ -71,15 +70,19 @@ Item { Layout.fillWidth: true color: root.colorScheme.background_weak - Rectangle { + Item { id: rightContent anchors.bottom: parent.bottom anchors.bottomMargin: 96 anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: 96 - color: "#ff0000" width: 444 + + OnboardingRightPane { + anchors.fill: parent + colorScheme: root.colorScheme + } } Label { id: reportProblemLink diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml new file mode 100644 index 00000000..652e7894 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml @@ -0,0 +1,68 @@ +// Copyright (c) 2023 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 . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.impl +import "." as Proton + +Item { + id: root + + property ColorScheme colorScheme + property string description + property string icon + property int iconSize: 64 + property string title + + implicitHeight: children[0].implicitHeight + implicitWidth: children[0].implicitWidth + + RowLayout { + anchors.fill: parent + spacing: 24 + + Image { + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + Layout.preferredHeight: iconSize + Layout.preferredWidth: iconSize + antialiasing: true + mipmap: true + source: root.icon + } + ColumnLayout { + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Layout.fillWidth: true + spacing: 8 + + Label { + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.fillHeight: false + Layout.fillWidth: true + colorScheme: root.colorScheme + text: root.title + type: Label.LabelType.Body_bold + } + Label { + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.fillHeight: true + Layout.fillWidth: true + colorScheme: root.colorScheme + text: root.description + type: Label.LabelType.Body + verticalAlignment: Text.AlignTop + } + } + } +} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-bridge.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-bridge.svg new file mode 100644 index 00000000..b2aa5fe6 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-bridge.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-mozilla-thunderbird.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-mozilla-thunderbird.svg index 83759ef0..6c41b98d 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-mozilla-thunderbird.svg +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-mozilla-thunderbird.svg @@ -1,112 +1,80 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-clients.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-clients.svg new file mode 100644 index 00000000..09e14678 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-clients.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From bd986901c3b0e9b9cbbdf47ad5dc89dd6957ade2 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Thu, 27 Jul 2023 13:48:52 +0200 Subject: [PATCH 12/93] feat(GODT-2767): login left pane. [skip-ci] --- .../bridge-gui/bridge-gui/Resources.qrc | 1 + .../qml/SetupWizard/LoginLeftPane.qml | 80 +++++++++++++++++++ .../qml/SetupWizard/OnboardingRightPane.qml | 15 ++-- .../qml/SetupWizard/SetupWizard.qml | 42 ++++++++-- 4 files changed, 124 insertions(+), 14 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginLeftPane.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index 488b8637..bc18612c 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -109,6 +109,7 @@ qml/SettingsView.qml qml/SetupGuide.qml qml/SetupWizard/SetupWizard.qml + qml/SetupWizard/LoginLeftPane.qml qml/SetupWizard/OnboardingLeftPane.qml qml/SetupWizard/OnboardingRightPane.qml qml/SetupWizard/StepDescriptionBox.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginLeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginLeftPane.qml new file mode 100644 index 00000000..34dcea05 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginLeftPane.qml @@ -0,0 +1,80 @@ +// Copyright (c) 2023 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 . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.impl +import "." as Proton + +Item { + id: root + + property ColorScheme colorScheme + property string description: qsTr("Let's start by signing in to your Proton account.") + + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + spacing: 0 + + Image { + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + Layout.preferredHeight: 72 + Layout.preferredWidth: 72 + fillMode: Image.PreserveAspectFit + source: "/qml/icons/ic-bridge.svg" + sourceSize.height: 128 + sourceSize.width: 128 + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Layout.topMargin: 16 + colorScheme: root.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Sign in to your Proton Account") + type: Label.LabelType.Heading + wrapMode: Text.WordWrap + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Layout.topMargin: 96 + colorScheme: root.colorScheme + horizontalAlignment: Text.AlignHCenter + text: description + type: Label.LabelType.Body + wrapMode: Text.WordWrap + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: false + Layout.topMargin: 96 + colorScheme: root.colorScheme + horizontalAlignment: Text.AlignHCenter + text: link("https://proton.me/mail/pricing", qsTr("Create or upgrade your account")) + type: Label.LabelType.Body + + onLinkActivated: function (link) { + Qt.openUrlExternally(link); + } + + HoverHandler { + acceptedDevices: PointerDevice.Mouse + cursorShape: Qt.PointingHandCursor + } + } + } +} \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/OnboardingRightPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/OnboardingRightPane.qml index 5a66de44..37880371 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/OnboardingRightPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/OnboardingRightPane.qml @@ -22,6 +22,8 @@ Item { property ColorScheme colorScheme + signal onboardingAccepted + ColumnLayout { anchors.left: parent.left anchors.right: parent.right @@ -38,24 +40,25 @@ Item { } StepDescriptionBox { colorScheme: root.colorScheme - description: "Connect Bridge to your Proton account" + description: qsTr("Connect Bridge to your Proton account") icon: "/qml/icons/ic-bridge.svg" - title: "Step 1" iconSize: 48 + title: qsTr("Step 1") } StepDescriptionBox { colorScheme: root.colorScheme - description: "Connect your email client to Bridge" + description: qsTr("Connect your email client to Bridge") icon: "/qml/icons/img-mail-clients.svg" - title: "Step 2" iconSize: 64 - + title: qsTr("Step 2") } Button { Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: 320 colorScheme: root.colorScheme - text: "Let's start" + text: qsTr("Let's start") + + onClicked: root.onboardingAccepted(); } } } \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index a142f6ff..2327355d 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -24,6 +24,13 @@ Item { function start() { root.visible = true; + leftContent.currentIndex = 0; + rightContent.currentIndex = 0; + } + function startLogin() { + root.visible = true; + leftContent.currentIndex = 1; + rightContent.currentIndex = 1; } RowLayout { @@ -36,17 +43,27 @@ Item { Layout.fillWidth: true color: root.colorScheme.background_norm - Item { + StackLayout { id: leftContent anchors.bottom: parent.bottom anchors.bottomMargin: 96 anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: 96 + currentIndex: 0 width: 444 + // stack index 0 OnboardingLeftPane { - anchors.fill: parent + Layout.fillHeight: true + Layout.fillWidth: true + colorScheme: root.colorScheme + } + + // stack index 1 + LoginLeftPane { + Layout.fillHeight: true + Layout.fillWidth: true colorScheme: root.colorScheme } } @@ -55,13 +72,10 @@ Item { anchors.bottom: parent.bottom anchors.bottomMargin: 48 anchors.horizontalCenter: parent.horizontalCenter - antialiasing: true fillMode: Image.PreserveAspectFit height: 24 - smooth: true source: root.colorScheme.mail_logo_with_wordmark - sourceSize.height: 24 - sourceSize.width: 142 + mipmap: true } } Rectangle { @@ -70,18 +84,30 @@ Item { Layout.fillWidth: true color: root.colorScheme.background_weak - Item { + StackLayout { id: rightContent anchors.bottom: parent.bottom anchors.bottomMargin: 96 anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: 96 + currentIndex: 0 width: 444 + // stack index 0 OnboardingRightPane { - anchors.fill: parent + Layout.fillHeight: true + Layout.fillWidth: true colorScheme: root.colorScheme + + onOnboardingAccepted: root.startLogin() + } + + // stack index 1 + Rectangle { + Layout.fillHeight: true + Layout.fillWidth: true + color: "#f00" } } Label { From c8f0d7f32aba86d8122afa1a378fdf8f5725b543 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Sat, 29 Jul 2023 08:00:00 +0200 Subject: [PATCH 13/93] feat(GODT-2767): login right pane, WIP. [skip-ci] --- .../bridge-gui/bridge-gui/Resources.qrc | 1 + .../qml/SetupWizard/LoginRightPane.qml | 420 ++++++++++++++++++ .../qml/SetupWizard/SetupWizard.qml | 22 +- 3 files changed, 440 insertions(+), 3 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginRightPane.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index bc18612c..fbb413d6 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -110,6 +110,7 @@ qml/SetupGuide.qml qml/SetupWizard/SetupWizard.qml qml/SetupWizard/LoginLeftPane.qml + qml/SetupWizard/LoginRightPane.qml qml/SetupWizard/OnboardingLeftPane.qml qml/SetupWizard/OnboardingRightPane.qml qml/SetupWizard/StepDescriptionBox.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginRightPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginRightPane.qml new file mode 100644 index 00000000..a6764d9f --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginRightPane.qml @@ -0,0 +1,420 @@ +// Copyright (c) 2023 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 . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.impl +import Proton + +FocusScope { + id: root + + property ColorScheme colorScheme + property alias currentIndex: stackLayout.currentIndex + property alias username: usernameTextField.text + + signal loginAbort(string username, bool wasSignedOut) + + function abort() { + root.reset(); + loginAbort(usernameTextField.text, false); + Backend.loginAbort(usernameTextField.text); + } + function reset(clearUsername = false) { + stackLayout.currentIndex = 0; + loginNormalLayout.reset(clearUsername); + login2FALayout.reset(); + login2PasswordLayout.reset(); + } + + implicitHeight: children[0].implicitHeight + implicitWidth: children[0].implicitWidth + state: "Page 1" + + states: [ + State { + name: "Page 1" + + PropertyChanges { + currentIndex: 0 + target: stackLayout + } + }, + State { + name: "Page 2" + + PropertyChanges { + currentIndex: 1 + target: stackLayout + } + }, + State { + name: "Page 3" + + PropertyChanges { + currentIndex: 2 + target: stackLayout + } + } + ] + + StackLayout { + id: stackLayout + function loginFailed() { + signInButton.loading = false; + usernameTextField.enabled = true; + usernameTextField.error = true; + passwordTextField.enabled = true; + passwordTextField.error = true; + } + + anchors.fill: parent + + Connections { + function onLogin2FAError(_) { + console.assert(stackLayout.currentIndex === 1, "Unexpected login2FAError"); + twoFAButton.loading = false; + twoFactorPasswordTextField.enabled = true; + twoFactorPasswordTextField.error = true; + twoFactorPasswordTextField.errorString = qsTr("Your code is incorrect"); + twoFactorPasswordTextField.focus = true; + } + function onLogin2FAErrorAbort(_) { + console.assert(stackLayout.currentIndex === 1, "Unexpected login2FAErrorAbort"); + root.reset(); + errorLabel.text = qsTr("Incorrect login credentials. Please try again."); + } + function onLogin2FARequested(username) { + console.assert(stackLayout.currentIndex === 0, "Unexpected login2FARequested"); + twoFactorUsernameLabel.text = username; + stackLayout.currentIndex = 1; + twoFactorPasswordTextField.focus = true; + } + function onLogin2PasswordError(_) { + console.assert(stackLayout.currentIndex === 2, "Unexpected login2PasswordError"); + secondPasswordButton.loading = false; + secondPasswordTextField.enabled = true; + secondPasswordTextField.error = true; + secondPasswordTextField.errorString = qsTr("Your mailbox password is incorrect"); + secondPasswordTextField.focus = true; + } + function onLogin2PasswordErrorAbort(_) { + console.assert(stackLayout.currentIndex === 2, "Unexpected login2PasswordErrorAbort"); + root.reset(); + errorLabel.text = qsTr("Incorrect login credentials. Please try again."); + } + function onLogin2PasswordRequested() { + console.assert(stackLayout.currentIndex === 0 || stackLayout.currentIndex === 1, "Unexpected login2PasswordRequested"); + stackLayout.currentIndex = 2; + secondPasswordTextField.focus = true; + } + function onLoginAlreadyLoggedIn(_) { + stackLayout.currentIndex = 0; + root.reset(); + } + function onLoginConnectionError(_) { + if (stackLayout.currentIndex === 0) { + stackLayout.loginFailed(); + } + } + function onLoginFinished(_) { + stackLayout.currentIndex = 0; + root.reset(); + } + function onLoginFreeUserError() { + console.assert(stackLayout.currentIndex === 0, "Unexpected loginFreeUserError"); + stackLayout.loginFailed(); + } + function onLoginUsernamePasswordError(errorMsg) { + console.assert(stackLayout.currentIndex === 0, "Unexpected loginUsernamePasswordError"); + stackLayout.loginFailed(); + if (errorMsg !== "") + errorLabel.text = errorMsg; + else + errorLabel.text = qsTr("Incorrect login credentials"); + } + + target: Backend + } + ColumnLayout { + id: loginNormalLayout + function reset(clearUsername = false) { + signInButton.loading = false; + errorLabel.text = ""; + usernameTextField.enabled = true; + usernameTextField.error = false; + usernameTextField.errorString = ""; + usernameTextField.focus = true; + if (clearUsername) { + usernameTextField.text = ""; + } + passwordTextField.enabled = true; + passwordTextField.error = false; + passwordTextField.errorString = ""; + passwordTextField.text = ""; + } + + spacing: 0 + + Label { + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: 16 + colorScheme: root.colorScheme + text: qsTr("Sign in") + type: Label.LabelType.Title + } + Label { + id: subTitle + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: 8 + color: root.colorScheme.text_weak + colorScheme: root.colorScheme + text: qsTr("Enter your Proton Account details.") + type: Label.LabelType.Body + } + RowLayout { + Layout.fillWidth: true + Layout.topMargin: 36 + spacing: 0 + visible: errorLabel.text.length > 0 + + ColorImage { + color: root.colorScheme.signal_danger + height: errorLabel.lineHeight + source: "/qml/icons/ic-exclamation-circle-filled.svg" + sourceSize.height: errorLabel.lineHeight + } + Label { + id: errorLabel + Layout.fillWidth: true + Layout.leftMargin: 4 + color: root.colorScheme.signal_danger + colorScheme: root.colorScheme + type: root.error ? Label.LabelType.Caption_semibold : Label.LabelType.Caption + wrapMode: Text.WordWrap + } + } + TextField { + id: usernameTextField + Layout.fillWidth: true + Layout.topMargin: 24 + colorScheme: root.colorScheme + focus: true + label: qsTr("Email or username") + validateOnEditingFinished: false + validator: function (str) { + if (str.length === 0) { + return qsTr("Enter email or username"); + } + } + + onAccepted: passwordTextField.forceActiveFocus() + onTextChanged: { + // remove "invalid username / password error" + if (error || errorLabel.text.length > 0) { + errorLabel.text = ""; + usernameTextField.error = false; + passwordTextField.error = false; + } + } + } + TextField { + id: passwordTextField + Layout.fillWidth: true + Layout.topMargin: 8 + colorScheme: root.colorScheme + echoMode: TextInput.Password + label: qsTr("Password") + validateOnEditingFinished: false + validator: function (str) { + if (str.length === 0) { + return qsTr("Enter password"); + } + } + + onAccepted: signInButton.checkAndSignIn() + onTextChanged: { + // remove "invalid username / password error" + if (error || errorLabel.text.length > 0) { + errorLabel.text = ""; + usernameTextField.error = false; + passwordTextField.error = false; + } + } + } + Button { + id: signInButton + function checkAndSignIn() { + usernameTextField.validate(); + passwordTextField.validate(); + if (usernameTextField.error || passwordTextField.error) { + return; + } + usernameTextField.enabled = false; + passwordTextField.enabled = false; + loading = true; + Backend.login(usernameTextField.text, Qt.btoa(passwordTextField.text)); + } + + Layout.fillWidth: true + Layout.topMargin: 24 + colorScheme: root.colorScheme + enabled: !loading + text: loading ? qsTr("Signing in") : qsTr("Sign in") + + onClicked: { + checkAndSignIn(); + } + } + Button { + id: cancelButton + Layout.fillWidth: true + Layout.topMargin: 24 + colorScheme: root.colorScheme + enabled: !loading + secondary: true + text: qsTr("Cancel") + + onClicked: { + root.abort(); + } + } + } + ColumnLayout { + id: login2FALayout + function reset() { + twoFAButton.loading = false; + twoFactorPasswordTextField.enabled = true; + twoFactorPasswordTextField.error = false; + twoFactorPasswordTextField.errorString = ""; + twoFactorPasswordTextField.text = ""; + } + + spacing: 0 + + Label { + Layout.alignment: Qt.AlignCenter + Layout.topMargin: 16 + colorScheme: root.colorScheme + text: qsTr("Two-factor authentication") + type: Label.LabelType.Heading + } + Label { + id: twoFactorUsernameLabel + Layout.alignment: Qt.AlignCenter + Layout.topMargin: 8 + color: root.colorScheme.text_weak + colorScheme: root.colorScheme + type: Label.LabelType.Lead + } + TextField { + id: twoFactorPasswordTextField + Layout.fillWidth: true + Layout.topMargin: 32 + assistiveText: qsTr("Enter the 6-digit code") + colorScheme: root.colorScheme + label: qsTr("Two-factor code") + validateOnEditingFinished: false + validator: function (str) { + if (str.length === 0) { + return qsTr("Enter the 6-digit code"); + } + } + + onAccepted: { + twoFAButton.onClicked(); + } + onTextChanged: { + if (text.length >= 6) { + twoFAButton.onClicked(); + } + } + } + Button { + id: twoFAButton + Layout.fillWidth: true + Layout.topMargin: 24 + colorScheme: root.colorScheme + enabled: !loading + text: loading ? qsTr("Authenticating") : qsTr("Authenticate") + + onClicked: { + twoFactorPasswordTextField.validate(); + if (twoFactorPasswordTextField.error) { + return; + } + twoFactorPasswordTextField.enabled = false; + loading = true; + Backend.login2FA(usernameTextField.text, Qt.btoa(twoFactorPasswordTextField.text)); + } + } + } + ColumnLayout { + id: login2PasswordLayout + function reset() { + secondPasswordButton.loading = false; + secondPasswordTextField.enabled = true; + secondPasswordTextField.error = false; + secondPasswordTextField.errorString = ""; + secondPasswordTextField.text = ""; + } + + spacing: 0 + + Label { + Layout.alignment: Qt.AlignCenter + Layout.topMargin: 16 + colorScheme: root.colorScheme + text: qsTr("Unlock your mailbox") + type: Label.LabelType.Heading + } + TextField { + id: secondPasswordTextField + Layout.fillWidth: true + Layout.topMargin: 8 + implicitHeight + 24 + subTitle.implicitHeight + colorScheme: root.colorScheme + echoMode: TextInput.Password + label: qsTr("Mailbox password") + validateOnEditingFinished: false + validator: function (str) { + if (str.length === 0) { + return qsTr("Enter password"); + } + } + + onAccepted: { + secondPasswordButton.onClicked(); + } + } + Button { + id: secondPasswordButton + Layout.fillWidth: true + Layout.topMargin: 24 + colorScheme: root.colorScheme + enabled: !loading + text: loading ? qsTr("Unlocking") : qsTr("Unlock") + + onClicked: { + secondPasswordTextField.validate(); + if (secondPasswordTextField.error) { + return; + } + secondPasswordTextField.enabled = false; + loading = true; + Backend.login2Password(usernameTextField.text, Qt.btoa(secondPasswordTextField.text)); + } + } + } + } +} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 2327355d..17b3d02e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -22,6 +22,9 @@ Item { property ColorScheme colorScheme + function closeWizard() { + root.visible = false; + } function start() { root.visible = true; leftContent.currentIndex = 0; @@ -31,8 +34,16 @@ Item { root.visible = true; leftContent.currentIndex = 1; rightContent.currentIndex = 1; + loginRightPane.reset(true); } + Connections { + function onLoginFinished() { + root.closeWizard(); + } + + target: Backend + } RowLayout { anchors.fill: parent spacing: 0 @@ -74,8 +85,8 @@ Item { anchors.horizontalCenter: parent.horizontalCenter fillMode: Image.PreserveAspectFit height: 24 - source: root.colorScheme.mail_logo_with_wordmark mipmap: true + source: root.colorScheme.mail_logo_with_wordmark } } Rectangle { @@ -104,10 +115,15 @@ Item { } // stack index 1 - Rectangle { + LoginRightPane { + id: loginRightPane Layout.fillHeight: true Layout.fillWidth: true - color: "#f00" + colorScheme: root.colorScheme + + onLoginAbort: { + root.closeWizard(); + } } } Label { From dd5e745e3795816a3281408ea64a811ecab6fab8 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Wed, 2 Aug 2023 09:33:58 +0200 Subject: [PATCH 14/93] feat(GODT-2767): login right pane. [skip-ci] --- .../qml/SetupWizard/LoginLeftPane.qml | 32 ++++++- .../qml/SetupWizard/LoginRightPane.qml | 85 +++++++++---------- .../qml/SetupWizard/SetupWizard.qml | 4 + 3 files changed, 76 insertions(+), 45 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginLeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginLeftPane.qml index 34dcea05..571dfad4 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginLeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginLeftPane.qml @@ -21,8 +21,32 @@ Item { id: root property ColorScheme colorScheme - property string description: qsTr("Let's start by signing in to your Proton account.") + property string description: "" + property string helpLink: "" + function show2FA() { + root.description = qsTr("You have enabled two-factor authentication. Please enter the 6-digit code provided by your authenticator application."); + root.helpLink = ""; + } + function showMailboxPassword() { + root.description = qsTr("You have secured your account with a separate mailbox password. "); + root.helpLink = ""; + } + function showSignIn() { + root.description = qsTr("Let's start by signing in to your Proton account."); + root.helpLink = linkLabel.link("https://proton.me/mail/pricing", qsTr("Create or upgrade your account")); + } + + Connections { + function onLogin2FARequested() { + show2FA(); + } + function onLogin2PasswordRequested() { + showMailboxPassword(); + } + + target: Backend + } ColumnLayout { anchors.left: parent.left anchors.right: parent.right @@ -59,13 +83,17 @@ Item { wrapMode: Text.WordWrap } Label { + id: linkLabel Layout.alignment: Qt.AlignHCenter Layout.fillWidth: false Layout.topMargin: 96 colorScheme: root.colorScheme horizontalAlignment: Text.AlignHCenter - text: link("https://proton.me/mail/pricing", qsTr("Create or upgrade your account")) + text: root.helpLink type: Label.LabelType.Body + visible: { + root.helpLink !== ""; + } onLinkActivated: function (link) { Qt.openUrlExternally(link); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginRightPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginRightPane.qml index a6764d9f..ad5a23f7 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginRightPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginRightPane.qml @@ -40,34 +40,6 @@ FocusScope { implicitHeight: children[0].implicitHeight implicitWidth: children[0].implicitWidth - state: "Page 1" - - states: [ - State { - name: "Page 1" - - PropertyChanges { - currentIndex: 0 - target: stackLayout - } - }, - State { - name: "Page 2" - - PropertyChanges { - currentIndex: 1 - target: stackLayout - } - }, - State { - name: "Page 3" - - PropertyChanges { - currentIndex: 2 - target: stackLayout - } - } - ] StackLayout { id: stackLayout @@ -169,10 +141,9 @@ FocusScope { Label { Layout.alignment: Qt.AlignHCenter - Layout.topMargin: 16 colorScheme: root.colorScheme text: qsTr("Sign in") - type: Label.LabelType.Title + type: Label.LabelType.Heading } Label { id: subTitle @@ -181,11 +152,11 @@ FocusScope { color: root.colorScheme.text_weak colorScheme: root.colorScheme text: qsTr("Enter your Proton Account details.") - type: Label.LabelType.Body + type: Label.LabelType.Lead } RowLayout { Layout.fillWidth: true - Layout.topMargin: 36 + Layout.topMargin: 48 spacing: 0 visible: errorLabel.text.length > 0 @@ -208,7 +179,7 @@ FocusScope { TextField { id: usernameTextField Layout.fillWidth: true - Layout.topMargin: 24 + Layout.topMargin: 48 colorScheme: root.colorScheme focus: true label: qsTr("Email or username") @@ -232,7 +203,7 @@ FocusScope { TextField { id: passwordTextField Layout.fillWidth: true - Layout.topMargin: 8 + Layout.topMargin: 48 colorScheme: root.colorScheme echoMode: TextInput.Password label: qsTr("Password") @@ -268,7 +239,7 @@ FocusScope { } Layout.fillWidth: true - Layout.topMargin: 24 + Layout.topMargin: 48 colorScheme: root.colorScheme enabled: !loading text: loading ? qsTr("Signing in") : qsTr("Sign in") @@ -278,11 +249,10 @@ FocusScope { } } Button { - id: cancelButton Layout.fillWidth: true - Layout.topMargin: 24 + Layout.topMargin: 32 colorScheme: root.colorScheme - enabled: !loading + enabled: !signInButton.loading secondary: true text: qsTr("Cancel") @@ -305,7 +275,6 @@ FocusScope { Label { Layout.alignment: Qt.AlignCenter - Layout.topMargin: 16 colorScheme: root.colorScheme text: qsTr("Two-factor authentication") type: Label.LabelType.Heading @@ -344,7 +313,7 @@ FocusScope { Button { id: twoFAButton Layout.fillWidth: true - Layout.topMargin: 24 + Layout.topMargin: 48 colorScheme: root.colorScheme enabled: !loading text: loading ? qsTr("Authenticating") : qsTr("Authenticate") @@ -359,6 +328,18 @@ FocusScope { Backend.login2FA(usernameTextField.text, Qt.btoa(twoFactorPasswordTextField.text)); } } + Button { + Layout.fillWidth: true + Layout.topMargin: 32 + colorScheme: root.colorScheme + enabled: !twoFAButton.loading + secondary: true + text: qsTr("Cancel") + + onClicked: { + root.abort(); + } + } } ColumnLayout { id: login2PasswordLayout @@ -374,15 +355,21 @@ FocusScope { Label { Layout.alignment: Qt.AlignCenter - Layout.topMargin: 16 colorScheme: root.colorScheme text: qsTr("Unlock your mailbox") type: Label.LabelType.Heading } + Label { + Layout.alignment: Qt.AlignCenter + Layout.topMargin: 8 + color: root.colorScheme.text_weak + colorScheme: root.colorScheme + type: Label.LabelType.Lead + } TextField { id: secondPasswordTextField Layout.fillWidth: true - Layout.topMargin: 8 + implicitHeight + 24 + subTitle.implicitHeight + Layout.topMargin: 48 colorScheme: root.colorScheme echoMode: TextInput.Password label: qsTr("Mailbox password") @@ -400,7 +387,7 @@ FocusScope { Button { id: secondPasswordButton Layout.fillWidth: true - Layout.topMargin: 24 + Layout.topMargin: 48 colorScheme: root.colorScheme enabled: !loading text: loading ? qsTr("Unlocking") : qsTr("Unlock") @@ -415,6 +402,18 @@ FocusScope { Backend.login2Password(usernameTextField.text, Qt.btoa(secondPasswordTextField.text)); } } + Button { + Layout.fillWidth: true + Layout.topMargin: 32 + colorScheme: root.colorScheme + enabled: !secondPasswordButton.loading + secondary: true + text: qsTr("Cancel") + + onClicked: { + root.abort(); + } + } } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 17b3d02e..183aae7e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -34,6 +34,7 @@ Item { root.visible = true; leftContent.currentIndex = 1; rightContent.currentIndex = 1; + loginLeftPane.showSignIn(); loginRightPane.reset(true); } @@ -61,6 +62,7 @@ Item { anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: 96 + clip: true currentIndex: 0 width: 444 @@ -73,6 +75,7 @@ Item { // stack index 1 LoginLeftPane { + id: loginLeftPane Layout.fillHeight: true Layout.fillWidth: true colorScheme: root.colorScheme @@ -103,6 +106,7 @@ Item { anchors.top: parent.top anchors.topMargin: 96 currentIndex: 0 + clip: true width: 444 // stack index 0 From 0fc41d1966832a79872e78cf1db2eb21fc3565f4 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Fri, 4 Aug 2023 09:59:06 +0200 Subject: [PATCH 15/93] feat(GODT-2767): unified left pane + client config left pane. [skip-ci] --- .../bridge-gui/bridge-gui/Resources.qrc | 5 +- .../bridge-gui/bridge-gui/qml/LinkLabel.qml | 41 +++++ .../bridge-gui/bridge-gui/qml/MainWindow.qml | 10 +- .../qml/SetupWizard/ClientConfigSelector.qml | 24 +++ .../bridge-gui/qml/SetupWizard/LeftPane.qml | 147 ++++++++++++++++++ .../qml/SetupWizard/LoginLeftPane.qml | 108 ------------- .../qml/SetupWizard/OnboardingLeftPane.qml | 78 ---------- .../qml/SetupWizard/SetupWizard.qml | 52 +++---- 8 files changed, 243 insertions(+), 222 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/LinkLabel.qml create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml delete mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginLeftPane.qml delete mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/OnboardingLeftPane.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index fbb413d6..544c927a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -75,6 +75,7 @@ ../../../../dist/bridgeMacOS.svg qml/KeychainSettings.qml qml/LocalCacheSettings.qml + qml/LinkLabel.qml qml/MainWindow.qml qml/NotificationDialog.qml qml/NotificationPopups.qml @@ -108,10 +109,10 @@ qml/SettingsItem.qml qml/SettingsView.qml qml/SetupGuide.qml + qml/SetupWizard/LeftPane.qml + qml/SetupWizard/ClientConfigSelector.qml qml/SetupWizard/SetupWizard.qml - qml/SetupWizard/LoginLeftPane.qml qml/SetupWizard/LoginRightPane.qml - qml/SetupWizard/OnboardingLeftPane.qml qml/SetupWizard/OnboardingRightPane.qml qml/SetupWizard/StepDescriptionBox.qml qml/SignIn.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/LinkLabel.qml b/internal/frontend/bridge-gui/bridge-gui/qml/LinkLabel.qml new file mode 100644 index 00000000..d5fc22ee --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/LinkLabel.qml @@ -0,0 +1,41 @@ +// Copyright (c) 2023 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 . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.impl +import "." as Proton + +Label { + id: root + + property string linkText + property string linkURL + + text: link(linkURL, linkText) + type: Label.LabelType.Body + + onLinkActivated: function (link) { + // if the link is "#", the user is indicating he will provide its own link activation handler. + if (link !== "#") { + Qt.openUrlExternally(link); + } + } + + HoverHandler { + acceptedDevices: PointerDevice.Mouse + cursorShape: Qt.PointingHandCursor + enabled: true + } +} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index 819b3dd7..35b3054c 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -95,11 +95,11 @@ ApplicationWindow { } Connections { function onLoginFinished(index, wasSignedOut) { - const user = Backend.users.get(index); - if (user && !wasSignedOut) { - root.showSetup(user, user.addresses[0]); - } - console.debug("Login finished", index); + // const user = Backend.users.get(index); + // if (user && !wasSignedOut) { + // root.showSetup(user, user.addresses[0]); + // } + // console.debug("Login finished", index); } function onSelectUser(userID, forceShowWindow) { contentWrapper.selectUser(userID); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml new file mode 100644 index 00000000..8074848a --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml @@ -0,0 +1,24 @@ +// Copyright (c) 2023 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 . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.impl +import Proton + +Rectangle { + id: root + property ColorScheme colorScheme + color: "#ff9900" +} \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml new file mode 100644 index 00000000..02250de8 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -0,0 +1,147 @@ +// Copyright (c) 2023 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 . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.impl +import "." as Proton +import ".." + +Item { + id: root + + property ColorScheme colorScheme + + function showLogin2FA() { + descriptionLabel.text = qsTr("You have enabled two-factor authentication. Please enter the 6-digit code provided by your authenticator application."); + linkLabel1.linkText = ""; + linkLabel1.linkURL = ""; + linkLabel2.linkText = ""; + linkLabel2.linkURL = ""; + showLoginCommon(); + } + function showClientSelector() { + titleLabel.text = qsTr("Configure your email client"); + descriptionLabel.text = qsTr("Bridge is now connected to Proton, and has already started downloading your messages. Let’s now connect your email client to Bridge."); + linkLabel1.linkText = qsTr("Why do"); + linkLabel1.linkURL = "https://proton.me"; + linkLabel2.linkText = qsTr("I need"); + linkLabel2.linkURL = "https://proton.me"; + icon.source = "/qml/icons/img-mail-clients.svg"; + icon.sourceSize.height = 128; + icon.sourceSize.width = 128; + Layout.preferredHeight = 72; + Layout.preferredWidth = 72; + } + function showLoginCommon() { + titleLabel.text = qsTr("Sign in to your Proton Account"); + icon.Layout.preferredHeight = 72; + icon.Layout.preferredWidth = 72; + icon.source = "/qml/icons/ic-bridge.svg"; + icon.sourceSize.height = 128; + icon.sourceSize.width = 128; + } + function showLoginMailboxPassword() { + root.description = qsTr("You have secured your account with a separate mailbox password."); + linkLabel1.linkText = ""; + linkLabel1.linkURL = ""; + linkLabel2.linkText = ""; + linkLabel2.linkURL = ""; + showLoginCommon(); + } + function showOnboarding() { + titleLabel.text = qsTr("Welcome to\nProton Mail Bridge"); + descriptionLabel.text = qsTr("Bridge is the gateway between your Proton account and your email client. It runs in the background and encrypts and decrypts your messages seamlessly. "); + linkLabel1.linkText = qsTr("Why do I need Bridge?"); + linkLabel1.linkURL = "https://proton.me/support/bridge"; + linkLabel2.linkText = ""; + linkLabel2.linkURL = ""; + icon.Layout.preferredHeight = 148; + icon.Layout.preferredWidth = 265; + icon.source = "/qml/icons/img-welcome.svg"; + icon.sourceSize.height = 148; + icon.sourceSize.width = 265; + } + function showLogin() { + descriptionLabel.text = qsTr("Let's start by signing in to your Proton account."); + linkLabel1.linkText = qsTr("Create or upgrade your account"); + linkLabel1.linkURL = "https://proton.me/mail/pricing"; + linkLabel2.linkText = ""; + linkLabel2.linkURL = ""; + showLoginCommon(); + } + Connections { + function onLogin2FARequested() { + showLogin2FA(); + } + function onLogin2PasswordRequested() { + showLoginMailboxPassword(); + } + + target: Backend + } + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + spacing: 0 + + Image { + id: icon + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + Layout.preferredHeight: 72 + Layout.preferredWidth: 72 + fillMode: Image.PreserveAspectFit + source: "" + sourceSize.height: 72 + sourceSize.width: 72 + } + Label { + id: titleLabel + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Layout.topMargin: 16 + colorScheme: root.colorScheme + horizontalAlignment: Text.AlignHCenter + text: "" + type: Label.LabelType.Heading + wrapMode: Text.WordWrap + } + Label { + id: descriptionLabel + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Layout.topMargin: 96 + colorScheme: root.colorScheme + horizontalAlignment: Text.AlignHCenter + text: "" + type: Label.LabelType.Body + wrapMode: Text.WordWrap + } + LinkLabel { + id: linkLabel1 + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: 96 + colorScheme: root.colorScheme + visible: (text !== "") + } + LinkLabel { + id: linkLabel2 + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: 16 + colorScheme: root.colorScheme + visible: (text !== "") + } + } +} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginLeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginLeftPane.qml deleted file mode 100644 index 571dfad4..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginLeftPane.qml +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) 2023 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 . -import QtQml -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import QtQuick.Controls.impl -import "." as Proton - -Item { - id: root - - property ColorScheme colorScheme - property string description: "" - property string helpLink: "" - - function show2FA() { - root.description = qsTr("You have enabled two-factor authentication. Please enter the 6-digit code provided by your authenticator application."); - root.helpLink = ""; - } - function showMailboxPassword() { - root.description = qsTr("You have secured your account with a separate mailbox password. "); - root.helpLink = ""; - } - function showSignIn() { - root.description = qsTr("Let's start by signing in to your Proton account."); - root.helpLink = linkLabel.link("https://proton.me/mail/pricing", qsTr("Create or upgrade your account")); - } - - Connections { - function onLogin2FARequested() { - show2FA(); - } - function onLogin2PasswordRequested() { - showMailboxPassword(); - } - - target: Backend - } - ColumnLayout { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - spacing: 0 - - Image { - Layout.alignment: Qt.AlignHCenter | Qt.AlignTop - Layout.preferredHeight: 72 - Layout.preferredWidth: 72 - fillMode: Image.PreserveAspectFit - source: "/qml/icons/ic-bridge.svg" - sourceSize.height: 128 - sourceSize.width: 128 - } - Label { - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - Layout.topMargin: 16 - colorScheme: root.colorScheme - horizontalAlignment: Text.AlignHCenter - text: qsTr("Sign in to your Proton Account") - type: Label.LabelType.Heading - wrapMode: Text.WordWrap - } - Label { - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - Layout.topMargin: 96 - colorScheme: root.colorScheme - horizontalAlignment: Text.AlignHCenter - text: description - type: Label.LabelType.Body - wrapMode: Text.WordWrap - } - Label { - id: linkLabel - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: false - Layout.topMargin: 96 - colorScheme: root.colorScheme - horizontalAlignment: Text.AlignHCenter - text: root.helpLink - type: Label.LabelType.Body - visible: { - root.helpLink !== ""; - } - - onLinkActivated: function (link) { - Qt.openUrlExternally(link); - } - - HoverHandler { - acceptedDevices: PointerDevice.Mouse - cursorShape: Qt.PointingHandCursor - } - } - } -} \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/OnboardingLeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/OnboardingLeftPane.qml deleted file mode 100644 index e8a6a538..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/OnboardingLeftPane.qml +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) 2023 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 . -import QtQml -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import QtQuick.Controls.impl -import "." as Proton - -Item { - id: root - - property ColorScheme colorScheme - - ColumnLayout { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - spacing: 0 - - Image { - Layout.alignment: Qt.AlignHCenter | Qt.AlignTop - Layout.preferredHeight: 148 - Layout.preferredWidth: 265 - antialiasing: true - source: "/qml/icons/img-welcome.svg" - sourceSize.height: 148 - sourceSize.width: 265 - } - Label { - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - Layout.topMargin: 16 - colorScheme: root.colorScheme - horizontalAlignment: Text.AlignHCenter - text: qsTr("Welcome to\nProton Mail Bridge") - type: Label.LabelType.Heading - } - Label { - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - Layout.topMargin: 96 - colorScheme: root.colorScheme - horizontalAlignment: Text.AlignHCenter - text: qsTr("Bridge is the gateway between your Proton account and your email client. It runs in the background and encrypts and decrypts your messages seamlessly. ") - type: Label.LabelType.Body - wrapMode: Text.WordWrap - } - Label { - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: false - Layout.topMargin: 48 - colorScheme: root.colorScheme - horizontalAlignment: Text.AlignHCenter - text: link("https://proton.me/support/bridge", qsTr("Why do I need Bridge?")) - type: Label.LabelType.Body - - onLinkActivated: function (link) { - Qt.openUrlExternally(link); - } - - HoverHandler { - acceptedDevices: PointerDevice.Mouse - cursorShape: Qt.PointingHandCursor - } - } - } -} \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 183aae7e..51a26043 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -16,6 +16,7 @@ import QtQuick.Layouts import QtQuick.Controls import QtQuick.Controls.impl import "." as Proton +import ".." Item { id: root @@ -27,20 +28,24 @@ Item { } function start() { root.visible = true; - leftContent.currentIndex = 0; + leftContent.showOnboarding(); rightContent.currentIndex = 0; } + function startClientConfig() { + root.visible = true; + leftContent.showClientSelector(); + rightContent.currentIndex = 2; + } function startLogin() { root.visible = true; - leftContent.currentIndex = 1; + leftContent.showLogin(); rightContent.currentIndex = 1; - loginLeftPane.showSignIn(); loginRightPane.reset(true); } Connections { function onLoginFinished() { - root.closeWizard(); + startClientConfig(); } target: Backend @@ -55,7 +60,7 @@ Item { Layout.fillWidth: true color: root.colorScheme.background_norm - StackLayout { + LeftPane { id: leftContent anchors.bottom: parent.bottom anchors.bottomMargin: 96 @@ -63,23 +68,8 @@ Item { anchors.top: parent.top anchors.topMargin: 96 clip: true - currentIndex: 0 + colorScheme: root.colorScheme width: 444 - - // stack index 0 - OnboardingLeftPane { - Layout.fillHeight: true - Layout.fillWidth: true - colorScheme: root.colorScheme - } - - // stack index 1 - LoginLeftPane { - id: loginLeftPane - Layout.fillHeight: true - Layout.fillWidth: true - colorScheme: root.colorScheme - } } Image { id: mailLogoWithWordmark @@ -105,8 +95,8 @@ Item { anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: 96 - currentIndex: 0 clip: true + currentIndex: 0 width: 444 // stack index 0 @@ -129,26 +119,30 @@ Item { root.closeWizard(); } } + + // stack index 2 + ClientConfigSelector { + id: clientCOnfigSelector + Layout.fillHeight: true + Layout.fillWidth: true + colorScheme: root.colorScheme + } } - Label { + LinkLabel { id: reportProblemLink anchors.bottom: parent.bottom anchors.bottomMargin: 48 anchors.horizontalCenter: parent.horizontalCenter colorScheme: root.colorScheme horizontalAlignment: Text.AlignRight - text: link("#", "Report problem") + linkText: qsTr("Report problem") + linkURL: "#" width: 444 onLinkActivated: { root.visible = false; } - HoverHandler { - id: mouse - acceptedDevices: PointerDevice.Mouse - cursorShape: Qt.PointingHandCursor - } } } } From 6c9d96d5e1ddba6934a493032755c8d032a1e461 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Mon, 7 Aug 2023 11:13:58 +0200 Subject: [PATCH 16/93] chore: fixed missing GoOs gRPC call in bridge-gui-tester. --- .../bridge-gui/bridge-gui-tester/GRPCService.cpp | 10 ++++++++++ .../bridge-gui/bridge-gui-tester/GRPCService.h | 1 + 2 files changed, 11 insertions(+) diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp index dc0cfb75..b1a1b3c4 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp @@ -214,6 +214,16 @@ grpc::Status GRPCService::IsTelemetryDisabled(::grpc::ServerContext *, ::google: } +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::GoOs(ServerContext *, Empty const*, StringValue *response) { + response->set_value(app().mainWindow().settingsTab().os().toStdString()); + return Status::OK; +} + + //**************************************************************************************************************************************************** /// \return The status for the call. //**************************************************************************************************************************************************** diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h index da5aa6c2..c4d5b9e9 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h @@ -53,6 +53,7 @@ public: // member functions. grpc::Status IsAllMailVisible(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::BoolValue *response) override; grpc::Status SetIsTelemetryDisabled(::grpc::ServerContext *, ::google::protobuf::BoolValue const *request, ::google::protobuf::Empty *response) override; grpc::Status IsTelemetryDisabled(::grpc::ServerContext *, ::google::protobuf::Empty const *request, ::google::protobuf::BoolValue *response) override; + grpc::Status GoOs(::grpc::ServerContext *, ::google::protobuf::Empty const*, ::google::protobuf::StringValue *response) override; grpc::Status TriggerReset(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::Empty *) override; grpc::Status Version(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override; grpc::Status LogsPath(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override; From 5d207810bd61c934e78e03ceacd2e5b0ac26116c Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Mon, 7 Aug 2023 17:15:24 +0200 Subject: [PATCH 17/93] feat(GODT-2767): client selection. [skip-ci] --- .../bridge-gui/bridge-gui/Resources.qrc | 1 + .../bridge-gui/bridge-gui/qml/LinkLabel.qml | 10 ++- .../bridge-gui/bridge-gui/qml/MainWindow.qml | 2 +- .../qml/SetupWizard/ClientConfigSelector.qml | 84 ++++++++++++++++++- .../qml/SetupWizard/ClientListItem.qml | 69 +++++++++++++++ .../bridge-gui/qml/SetupWizard/LeftPane.qml | 30 +++---- .../qml/SetupWizard/SetupWizard.qml | 24 ++++-- 7 files changed, 185 insertions(+), 35 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientListItem.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index 544c927a..946f2618 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -109,6 +109,7 @@ qml/SettingsItem.qml qml/SettingsView.qml qml/SetupGuide.qml + qml/SetupWizard/ClientListItem.qml qml/SetupWizard/LeftPane.qml qml/SetupWizard/ClientConfigSelector.qml qml/SetupWizard/SetupWizard.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/LinkLabel.qml b/internal/frontend/bridge-gui/bridge-gui/qml/LinkLabel.qml index d5fc22ee..0dae6e7f 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/LinkLabel.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/LinkLabel.qml @@ -19,11 +19,13 @@ import "." as Proton Label { id: root + function clear() { + text = ""; + } + function setLink(linkURL, linkText) { + text = link(linkURL, linkText); + } - property string linkText - property string linkURL - - text: link(linkURL, linkText) type: Label.LabelType.Body onLinkActivated: function (link) { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index 35b3054c..3e725f4d 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -167,7 +167,7 @@ ApplicationWindow { Backend.quit(); } onShowSetupGuide: function (user, address) { - root.showSetup(user, address); + setupWizard.startClientConfig(); } onShowSetupWizard: { setupWizard.start(); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml index 8074848a..ce0335c2 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml @@ -17,8 +17,84 @@ import QtQuick.Controls import QtQuick.Controls.impl import Proton -Rectangle { +Item { id: root - property ColorScheme colorScheme - color: "#ff9900" -} \ No newline at end of file + + property ColorScheme colorScheme: wizard.colorScheme + readonly property bool onMacOS: (Backend.goos === "darwin") + readonly property bool onWindows: (Backend.goos === "windows") + property var wizard + + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + spacing: 0 + + Label { + Layout.alignment: Qt.AlignHCenter + colorScheme: root.colorScheme + text: qsTr("Select your email application") + type: Label.LabelType.Heading + } + Item { + Layout.preferredHeight: 72 + } + ClientListItem { + Layout.fillWidth: true + colorScheme: root.colorScheme + iconSource: "/qml/icons/ic-apple-mail.svg" + text: "Apple Mail" + visible: root.onMacOS + + onClicked: { + wizard.client = SetupWizard.Client.AppleMail; + } + } + ClientListItem { + Layout.fillWidth: true + colorScheme: root.colorScheme + iconSource: "/qml/icons/ic-microsoft-outlook.svg" + text: "Microsoft Outlook" + visible: root.onMacOS || root.onWindows + + onClicked: { + wizard.client = SetupWizard.Client.MicrosoftOutlook; + } + } + ClientListItem { + Layout.fillWidth: true + colorScheme: root.colorScheme + iconSource: "/qml/icons/ic-mozilla-thunderbird.svg" + text: "Mozilla Thunderbird" + + onClicked: { + wizard.client = SetupWizard.Client.MozillaThunderbird; + } + } + ClientListItem { + Layout.fillWidth: true + colorScheme: root.colorScheme + iconSource: "/qml/icons/ic-other-mail-clients.svg" + text: "Other" + + onClicked: { + wizard.client = SetupWizard.Client.Generic; + } + } + Item { + Layout.preferredHeight: 72 + } + Button { + Layout.fillWidth: true + colorScheme: root.colorScheme + secondary: true + text: qsTr("Cancel") + + onClicked: { + root.wizard.closeWizard(); + } + } + } +} + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientListItem.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientListItem.qml new file mode 100644 index 00000000..da92fcac --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientListItem.qml @@ -0,0 +1,69 @@ +// Copyright (c) 2023 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 . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.impl +import Proton + +Item { + id: root + + property ColorScheme colorScheme + property string iconSource + property string text + + signal clicked + + implicitHeight: clientRow.height + + ColumnLayout { + id: clientRow + anchors.left: parent.left + anchors.right: parent.right + spacing: 0 + + RowLayout { + Layout.bottomMargin: 12 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.topMargin: 12 + + ColorImage { + height: 36 + source: iconSource + sourceSize.height: 36 + } + Label { + Layout.leftMargin: 12 + colorScheme: root.colorScheme + text: root.text + type: Label.LabelType.Body + } + } + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: root.colorScheme.border_weak + } + } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + + onClicked: { + root.clicked(); + } + } +} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 02250de8..752d4311 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -25,19 +25,15 @@ Item { function showLogin2FA() { descriptionLabel.text = qsTr("You have enabled two-factor authentication. Please enter the 6-digit code provided by your authenticator application."); - linkLabel1.linkText = ""; - linkLabel1.linkURL = ""; - linkLabel2.linkText = ""; - linkLabel2.linkURL = ""; + linkLabel1.clear(); + linkLabel2.clear(); showLoginCommon(); } function showClientSelector() { titleLabel.text = qsTr("Configure your email client"); descriptionLabel.text = qsTr("Bridge is now connected to Proton, and has already started downloading your messages. Let’s now connect your email client to Bridge."); - linkLabel1.linkText = qsTr("Why do"); - linkLabel1.linkURL = "https://proton.me"; - linkLabel2.linkText = qsTr("I need"); - linkLabel2.linkURL = "https://proton.me"; + linkLabel1.clear(); + linkLabel2.clear(); icon.source = "/qml/icons/img-mail-clients.svg"; icon.sourceSize.height = 128; icon.sourceSize.width = 128; @@ -54,19 +50,15 @@ Item { } function showLoginMailboxPassword() { root.description = qsTr("You have secured your account with a separate mailbox password."); - linkLabel1.linkText = ""; - linkLabel1.linkURL = ""; - linkLabel2.linkText = ""; - linkLabel2.linkURL = ""; + linkLabel1.clear(); + linkLabel2.clear(); showLoginCommon(); } function showOnboarding() { titleLabel.text = qsTr("Welcome to\nProton Mail Bridge"); descriptionLabel.text = qsTr("Bridge is the gateway between your Proton account and your email client. It runs in the background and encrypts and decrypts your messages seamlessly. "); - linkLabel1.linkText = qsTr("Why do I need Bridge?"); - linkLabel1.linkURL = "https://proton.me/support/bridge"; - linkLabel2.linkText = ""; - linkLabel2.linkURL = ""; + linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why do I need Bridge?")); + linkLabel2.clear(); icon.Layout.preferredHeight = 148; icon.Layout.preferredWidth = 265; icon.source = "/qml/icons/img-welcome.svg"; @@ -75,10 +67,8 @@ Item { } function showLogin() { descriptionLabel.text = qsTr("Let's start by signing in to your Proton account."); - linkLabel1.linkText = qsTr("Create or upgrade your account"); - linkLabel1.linkURL = "https://proton.me/mail/pricing"; - linkLabel2.linkText = ""; - linkLabel2.linkURL = ""; + linkLabel1.setLink("https://proton.me/mail/pricing", qsTr("Create or upgrade your account")); + linkLabel2.clear(); showLoginCommon(); } Connections { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 51a26043..6f4d91ef 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -20,8 +20,19 @@ import ".." Item { id: root + enum Client { + AppleMail, + MicrosoftOutlook, + MozillaThunderbird, + Generic + } + property string address + property int client + property string clientVersion property ColorScheme colorScheme + property string userID + property bool wasSignedOut function closeWizard() { root.visible = false; @@ -36,8 +47,11 @@ Item { leftContent.showClientSelector(); rightContent.currentIndex = 2; } - function startLogin() { + function startLogin(wasSignedOut = false) { root.visible = true; + root.userID = ""; + root.address = ""; + root.wasSignedOut = wasSignedOut; leftContent.showLogin(); rightContent.currentIndex = 1; loginRightPane.reset(true); @@ -122,10 +136,10 @@ Item { // stack index 2 ClientConfigSelector { - id: clientCOnfigSelector + id: clientConfigSelector Layout.fillHeight: true Layout.fillWidth: true - colorScheme: root.colorScheme + wizard: root } } LinkLabel { @@ -135,14 +149,12 @@ Item { anchors.horizontalCenter: parent.horizontalCenter colorScheme: root.colorScheme horizontalAlignment: Text.AlignRight - linkText: qsTr("Report problem") - linkURL: "#" + text: link("#", qsTr("Report problem")) width: 444 onLinkActivated: { root.visible = false; } - } } } From a35c8424a3ca054aa7287114a07a6b6f676b09fb Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Tue, 8 Aug 2023 08:49:37 +0200 Subject: [PATCH 18/93] chore: fix after rebase. --- internal/frontend/bridge-gui/bridge-gui/Resources.qrc | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index 946f2618..180a9f7c 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -5,7 +5,6 @@ qml/AccountView.qml qml/Banner.qml qml/Bridge.qml - qml/bridgeqml.qmlproject qml/BugCategoryView.qml qml/BugQuestionView.qml qml/BugReportFlow.qml @@ -103,7 +102,6 @@ qml/Proton/TextField.qml qml/Proton/Toggle.qml qml/Proton/WebView.qml - qml/Proton/WebViewOverlay.qml qml/QuestionItem.qml qml/Resources/bug_report_flow.json qml/SettingsItem.qml From df02e39fe1a7b0dc86b16281ddce92d94dbf93b8 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Tue, 8 Aug 2023 10:40:42 +0200 Subject: [PATCH 19/93] feat(GODT-2767): Outlook version selector and warning screen. --- .../bridge-gui/bridge-gui/Resources.qrc | 2 + .../ClientConfigOutlookSelector.qml | 92 +++++++++++++++ .../qml/SetupWizard/ClientConfigSelector.qml | 2 + .../qml/SetupWizard/ClientConfigWarning.qml | 110 ++++++++++++++++++ .../bridge-gui/qml/SetupWizard/LeftPane.qml | 32 +++-- .../qml/SetupWizard/SetupWizard.qml | 27 +++++ 6 files changed, 255 insertions(+), 10 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigOutlookSelector.qml create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigWarning.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index 180a9f7c..2eb690cf 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -109,7 +109,9 @@ qml/SetupGuide.qml qml/SetupWizard/ClientListItem.qml qml/SetupWizard/LeftPane.qml + qml/SetupWizard/ClientConfigOutlookSelector.qml qml/SetupWizard/ClientConfigSelector.qml + qml/SetupWizard/ClientConfigWarning.qml qml/SetupWizard/SetupWizard.qml qml/SetupWizard/LoginRightPane.qml qml/SetupWizard/OnboardingRightPane.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigOutlookSelector.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigOutlookSelector.qml new file mode 100644 index 00000000..5843eb5a --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigOutlookSelector.qml @@ -0,0 +1,92 @@ +// Copyright (c) 2023 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 . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.impl +import Proton + +Item { + id: root + + property ColorScheme colorScheme: wizard.colorScheme + readonly property bool onMacOS: (Backend.goos === "darwin") + readonly property bool onWindows: (Backend.goos === "windows") + property var wizard + + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + spacing: 0 + + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: root.colorScheme + text: qsTr("Pick your version of Outlook") + type: Label.LabelType.Heading + } + Item { + Layout.preferredHeight: 72 + } + ClientListItem { + Layout.fillWidth: true + colorScheme: root.colorScheme + iconSource: "/qml/icons/ic-microsoft-outlook.svg" + text: "Outlook from Microsoft 365" + + onClicked: { + wizard.clientVersion = "365"; + wizard.showClientWarning(); + } + } + ClientListItem { + Layout.fillWidth: true + colorScheme: root.colorScheme + iconSource: "/qml/icons/ic-microsoft-outlook.svg" + text: "Outlook 2019" + + onClicked: { + wizard.clientVersion = "2019"; + wizard.showClientWarning(); + } + } + ClientListItem { + Layout.fillWidth: true + colorScheme: root.colorScheme + iconSource: "/qml/icons/ic-microsoft-outlook.svg" + text: "Outlook 2016" + + onClicked: { + wizard.clientVersion = "2016"; + wizard.showClientWarning(); + } + } + Item { + Layout.preferredHeight: 72 + } + Button { + Layout.fillWidth: true + colorScheme: root.colorScheme + secondary: true + text: qsTr("Cancel") + + onClicked: { + root.wizard.closeWizard(); + } + } + } +} + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml index ce0335c2..1081c75c 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml @@ -33,6 +33,7 @@ Item { Label { Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true colorScheme: root.colorScheme text: qsTr("Select your email application") type: Label.LabelType.Heading @@ -60,6 +61,7 @@ Item { onClicked: { wizard.client = SetupWizard.Client.MicrosoftOutlook; + wizard.showOutlookSelector(); } } ClientListItem { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigWarning.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigWarning.qml new file mode 100644 index 00000000..039bcc95 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigWarning.qml @@ -0,0 +1,110 @@ +// Copyright (c) 2023 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 . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.impl +import Proton + +// Copyright (c) 2023 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 . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.impl +import Proton + +Item { + id: root + + property ColorScheme colorScheme: wizard.colorScheme + property var wizard + + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + spacing: 0 + + Label { + Layout.fillWidth: true + colorScheme: root.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("A word of warning") + type: Label.LabelType.Heading + wrapMode: Text.WordWrap + } + Item { + Layout.preferredHeight: 96 + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + colorScheme: root.colorScheme + text: qsTr("Do not enter your Proton account password in you email application.") + type: Label.LabelType.Body_bold + wrapMode: Text.WordWrap + } + Item { + Layout.preferredHeight: 96 + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: root.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("We have generated a new password for you. It will work only on this computer, and can safely be entered in your email client.") + type: Label.LabelType.Body + wrapMode: Text.WordWrap + } + Item { + Layout.preferredHeight: 96 + } + Button { + Layout.fillWidth: true + colorScheme: root.colorScheme + text: qsTr("I understand") + + onClicked: { + root.wizard.closeWizard(); + } + } + Item { + Layout.preferredHeight: 32 + } + Button { + Layout.fillWidth: true + colorScheme: root.colorScheme + secondary: true + text: qsTr("Cancel") + + onClicked: { + root.wizard.closeWizard(); + } + } + } +} + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 752d4311..df16ad11 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -23,12 +23,6 @@ Item { property ColorScheme colorScheme - function showLogin2FA() { - descriptionLabel.text = qsTr("You have enabled two-factor authentication. Please enter the 6-digit code provided by your authenticator application."); - linkLabel1.clear(); - linkLabel2.clear(); - showLoginCommon(); - } function showClientSelector() { titleLabel.text = qsTr("Configure your email client"); descriptionLabel.text = qsTr("Bridge is now connected to Proton, and has already started downloading your messages. Let’s now connect your email client to Bridge."); @@ -40,6 +34,18 @@ Item { Layout.preferredHeight = 72; Layout.preferredWidth = 72; } + function showLogin() { + descriptionLabel.text = qsTr("Let's start by signing in to your Proton account."); + linkLabel1.setLink("https://proton.me/mail/pricing", qsTr("Create or upgrade your account")); + linkLabel2.clear(); + showLoginCommon(); + } + function showLogin2FA() { + descriptionLabel.text = qsTr("You have enabled two-factor authentication. Please enter the 6-digit code provided by your authenticator application."); + linkLabel1.clear(); + linkLabel2.clear(); + showLoginCommon(); + } function showLoginCommon() { titleLabel.text = qsTr("Sign in to your Proton Account"); icon.Layout.preferredHeight = 72; @@ -65,12 +71,18 @@ Item { icon.sourceSize.height = 148; icon.sourceSize.width = 265; } - function showLogin() { - descriptionLabel.text = qsTr("Let's start by signing in to your Proton account."); - linkLabel1.setLink("https://proton.me/mail/pricing", qsTr("Create or upgrade your account")); + function showOutlookSelector() { + titleLabel.text = qsTr("Configure Outlook"); + descriptionLabel.text = qsTr("We will now guide you through the process of setting up your Proton account in Outlook."); + linkLabel1.setLink("https://proton.me/support/bridge", qsTr("My version of Outlook is not listed")); linkLabel2.clear(); - showLoginCommon(); + icon.Layout.preferredHeight = 72; + icon.Layout.preferredWidth = 72; + icon.source = "/qml/icons/ic-microsoft-outlook.svg"; + icon.sourceSize.height = 128; + icon.sourceSize.width = 128; } + Connections { function onLogin2FARequested() { showLogin2FA(); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 6f4d91ef..d7e70a85 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -37,6 +37,12 @@ Item { function closeWizard() { root.visible = false; } + function showOutlookSelector() { + console.error("showOutlookSelector()"); + root.visible = true; + leftContent.showOutlookSelector(); + rightContent.currentIndex = 3; + } function start() { root.visible = true; leftContent.showOnboarding(); @@ -57,6 +63,13 @@ Item { loginRightPane.reset(true); } + function showClientWarning() { + console.error("showClientWarning()"); + root.visible = true; + //leftContent.showWarning(); + rightContent.currentIndex = 4 + } + Connections { function onLoginFinished() { startClientConfig(); @@ -141,6 +154,20 @@ Item { Layout.fillWidth: true wizard: root } + // stack index 3 + ClientConfigOutlookSelector { + id: clientConfigOutlookSelector + Layout.fillHeight: true + Layout.fillWidth: true + wizard: root + } + // stack index 4 + ClientConfigWarning { + id: clientConfigWarning + Layout.fillHeight: true + Layout.fillWidth: true + wizard: root + } } LinkLabel { id: reportProblemLink From 83b842b19dbfa29ecbf81f049057688efbd30f8f Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Tue, 8 Aug 2023 16:55:05 +0200 Subject: [PATCH 20/93] feat(GODT-2767): per client configuration left pane + refactoring. [skip-ci] --- .../bridge-gui/bridge-gui/Resources.qrc | 4 +- .../qml/SetupWizard/ClientConfigSelector.qml | 2 + .../bridge-gui/qml/SetupWizard/LeftPane.qml | 29 ++- .../{LoginRightPane.qml => Login.qml} | 0 ...OnboardingRightPane.qml => Onboarding.qml} | 0 .../qml/SetupWizard/SetupWizard.qml | 46 ++++- .../qml/icons/ic-mozilla-thunderbird.svg | 190 ++++++++++-------- 7 files changed, 175 insertions(+), 96 deletions(-) rename internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/{LoginRightPane.qml => Login.qml} (100%) rename internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/{OnboardingRightPane.qml => Onboarding.qml} (100%) diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index 2eb690cf..30269cb5 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -113,8 +113,8 @@ qml/SetupWizard/ClientConfigSelector.qml qml/SetupWizard/ClientConfigWarning.qml qml/SetupWizard/SetupWizard.qml - qml/SetupWizard/LoginRightPane.qml - qml/SetupWizard/OnboardingRightPane.qml + qml/SetupWizard/Login.qml + qml/SetupWizard/Onboarding.qml qml/SetupWizard/StepDescriptionBox.qml qml/SignIn.qml qml/ConnectionModeSettings.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml index 1081c75c..9fb87bc8 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml @@ -72,6 +72,7 @@ Item { onClicked: { wizard.client = SetupWizard.Client.MozillaThunderbird; + wizard.showClientWarning(); } } ClientListItem { @@ -82,6 +83,7 @@ Item { onClicked: { wizard.client = SetupWizard.Client.Generic; + wizard.showClientWarning(); } } Item { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index df16ad11..5e9e59d0 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -20,7 +20,7 @@ import ".." Item { id: root - + property var wizard property ColorScheme colorScheme function showClientSelector() { @@ -29,23 +29,38 @@ Item { linkLabel1.clear(); linkLabel2.clear(); icon.source = "/qml/icons/img-mail-clients.svg"; + } + + function showClientConfigCommon() { + const clientName = wizard.clientName(); + titleLabel.text = qsTr("Configure %1").arg(clientName); + descriptionLabel.text = qsTr("We will now guide you through the process of setting up your Proton account in %1.").arg(clientName); + icon.source = wizard.clientIconSource(); icon.sourceSize.height = 128; icon.sourceSize.width = 128; Layout.preferredHeight = 72; Layout.preferredWidth = 72; } + + function showClientConfigWarning() { + showClientConfigCommon(); + linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why can't I use my Proton password in my email client?")); + } + function showLogin() { descriptionLabel.text = qsTr("Let's start by signing in to your Proton account."); linkLabel1.setLink("https://proton.me/mail/pricing", qsTr("Create or upgrade your account")); linkLabel2.clear(); showLoginCommon(); } + function showLogin2FA() { descriptionLabel.text = qsTr("You have enabled two-factor authentication. Please enter the 6-digit code provided by your authenticator application."); linkLabel1.clear(); linkLabel2.clear(); showLoginCommon(); } + function showLoginCommon() { titleLabel.text = qsTr("Sign in to your Proton Account"); icon.Layout.preferredHeight = 72; @@ -54,12 +69,14 @@ Item { icon.sourceSize.height = 128; icon.sourceSize.width = 128; } + function showLoginMailboxPassword() { root.description = qsTr("You have secured your account with a separate mailbox password."); linkLabel1.clear(); linkLabel2.clear(); showLoginCommon(); } + function showOnboarding() { titleLabel.text = qsTr("Welcome to\nProton Mail Bridge"); descriptionLabel.text = qsTr("Bridge is the gateway between your Proton account and your email client. It runs in the background and encrypts and decrypts your messages seamlessly. "); @@ -71,22 +88,18 @@ Item { icon.sourceSize.height = 148; icon.sourceSize.width = 265; } + function showOutlookSelector() { - titleLabel.text = qsTr("Configure Outlook"); - descriptionLabel.text = qsTr("We will now guide you through the process of setting up your Proton account in Outlook."); + showClientConfigCommon(); linkLabel1.setLink("https://proton.me/support/bridge", qsTr("My version of Outlook is not listed")); linkLabel2.clear(); - icon.Layout.preferredHeight = 72; - icon.Layout.preferredWidth = 72; - icon.source = "/qml/icons/ic-microsoft-outlook.svg"; - icon.sourceSize.height = 128; - icon.sourceSize.width = 128; } Connections { function onLogin2FARequested() { showLogin2FA(); } + function onLogin2PasswordRequested() { showLoginMailboxPassword(); } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginRightPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml similarity index 100% rename from internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LoginRightPane.qml rename to internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/OnboardingRightPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml similarity index 100% rename from internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/OnboardingRightPane.qml rename to internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index d7e70a85..d116c623 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -34,11 +34,42 @@ Item { property string userID property bool wasSignedOut + function clientIconSource() { + switch (client) { + case SetupWizard.Client.AppleMail: + return "/qml/icons/ic-apple-mail.svg"; + case SetupWizard.Client.MicrosoftOutlook: + return "/qml/icons/ic-microsoft-outlook.svg"; + case SetupWizard.Client.MozillaThunderbird: + return "/qml/icons/ic-mozilla-thunderbird.svg"; + case SetupWizard.Client.Generic: + return "/qml/icons/ic-other-mail-clients.svg"; + default: + console.error("Unknown mail client " + client) + return "/qml/icons/ic-other-mail-clients.svg"; + } + } + + function clientName() { + switch (client) { + case SetupWizard.Client.AppleMail: + return "Apple Mail"; + case SetupWizard.Client.MicrosoftOutlook: + return "Outlook"; + case SetupWizard.Client.MozillaThunderbird: + return "Thunderbird"; + case SetupWizard.Client.Generic: + return "your email client"; + default: + console.error("Unknown mail client " + client) + return "your email client"; + } + } + function closeWizard() { root.visible = false; } function showOutlookSelector() { - console.error("showOutlookSelector()"); root.visible = true; leftContent.showOutlookSelector(); rightContent.currentIndex = 3; @@ -60,13 +91,12 @@ Item { root.wasSignedOut = wasSignedOut; leftContent.showLogin(); rightContent.currentIndex = 1; - loginRightPane.reset(true); + login.reset(true); } function showClientWarning() { - console.error("showClientWarning()"); root.visible = true; - //leftContent.showWarning(); + leftContent.showClientConfigWarning(); rightContent.currentIndex = 4 } @@ -89,6 +119,7 @@ Item { LeftPane { id: leftContent + anchors.bottom: parent.bottom anchors.bottomMargin: 96 anchors.horizontalCenter: parent.horizontalCenter @@ -97,6 +128,7 @@ Item { clip: true colorScheme: root.colorScheme width: 444 + wizard: root } Image { id: mailLogoWithWordmark @@ -127,7 +159,7 @@ Item { width: 444 // stack index 0 - OnboardingRightPane { + Onboarding { Layout.fillHeight: true Layout.fillWidth: true colorScheme: root.colorScheme @@ -136,8 +168,8 @@ Item { } // stack index 1 - LoginRightPane { - id: loginRightPane + Login { + id: login Layout.fillHeight: true Layout.fillWidth: true colorScheme: root.colorScheme diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-mozilla-thunderbird.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-mozilla-thunderbird.svg index 6c41b98d..83759ef0 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-mozilla-thunderbird.svg +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-mozilla-thunderbird.svg @@ -1,80 +1,112 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 9ef7d133c0233597f6c0bbbaae159c609f82d1f1 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Wed, 9 Aug 2023 18:58:59 +0200 Subject: [PATCH 21/93] feat(GODT-2767): client config page. [skip-ci] --- .../bridge-gui/bridge-gui/Resources.qrc | 1 + .../SetupWizard/ClientConfigParameters.qml | 128 +++++++++++ .../qml/SetupWizard/ClientConfigWarning.qml | 4 +- .../bridge-gui/qml/SetupWizard/Onboarding.qml | 2 +- .../qml/SetupWizard/SetupWizard.qml | 213 ++++++++++-------- 5 files changed, 254 insertions(+), 94 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index 30269cb5..d3651c83 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -110,6 +110,7 @@ qml/SetupWizard/ClientListItem.qml qml/SetupWizard/LeftPane.qml qml/SetupWizard/ClientConfigOutlookSelector.qml + qml/SetupWizard/ClientConfigParameters.qml qml/SetupWizard/ClientConfigSelector.qml qml/SetupWizard/ClientConfigWarning.qml qml/SetupWizard/SetupWizard.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml new file mode 100644 index 00000000..426581fa --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml @@ -0,0 +1,128 @@ +// Copyright (c) 2023 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 . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.impl +import Proton +import ".." + +Rectangle { + id: root + property var wizard + property ColorScheme colorScheme: wizard.colorScheme + color: colorScheme.background_weak + readonly property bool genericClient: SetupWizard.Client.Generic === wizard.client + + Item { + id: centeredContainer + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.bottom: parent.bottom + width: 800 + + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.bottomMargin: 96 + anchors.topMargin: 32 + spacing: 0 + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: root.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Configure %1").arg(wizard.clientName()) + type: Label.LabelType.Heading + wrapMode: Text.WordWrap + } + Label { + id: descriptionLabel + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Layout.topMargin: 8 + color: colorScheme.text_weak + colorScheme: root.colorScheme + horizontalAlignment: Text.AlignHCenter + text: genericClient ? qsTr("Here are the IMAP and SMTP configuration parameters for your email client") : + qsTr("Here are your email configuration parameters for %1. \nWe have prepared an easy to follow configuration guide to help you setup your account in %1.").arg(wizard.clientName()) + type: Label.LabelType.Body + wrapMode: Text.WordWrap + } + RowLayout { + id: configuration + + property string currentAddress: wizard.user ? wizard.user.address : "" + + Layout.fillHeight: true + Layout.fillWidth: true + Layout.topMargin: 32 + spacing: 64 + Configuration { + Layout.fillWidth: true + colorScheme: root.colorScheme + hostname: Backend.hostname + password: wizard.user ? wizard.user.password : "" + port: Backend.imapPort.toString() + security: Backend.useSSLForIMAP ? "SSL" : "STARTTLS" + title: qsTr("IMAP") + username: configuration.currentAddress + } + Configuration { + Layout.fillWidth: true + colorScheme: root.colorScheme + hostname: Backend.hostname + password: wizard.user ? wizard.user.password : "" + port: Backend.smtpPort.toString() + security: Backend.useSSLForSMTP ? "SSL" : "STARTTLS" + title: qsTr("SMTP") + username: configuration.currentAddress + } + } + + Button { + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: 444 + Layout.topMargin: 32 + colorScheme: root.colorScheme + text: qsTr("Open configuration guide") + visible: !genericClient + } + + Button { + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: 444 + Layout.topMargin: 32 + colorScheme: root.colorScheme + text: qsTr("Done") + onClicked: root.wizard.closeWizard() + } + } + + LinkLabel { + id: reportProblemLink + anchors.bottom: parent.bottom + anchors.bottomMargin: 48 + anchors.right: parent.right + colorScheme: root.colorScheme + horizontalAlignment: Text.AlignRight + text: link("#", qsTr("Report problem")) + + onLinkActivated: { + wizard.closeWizard(); + } + } + } +} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigWarning.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigWarning.qml index 039bcc95..60bab587 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigWarning.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigWarning.qml @@ -65,7 +65,7 @@ Item { horizontalAlignment: Text.AlignHCenter colorScheme: root.colorScheme text: qsTr("Do not enter your Proton account password in you email application.") - type: Label.LabelType.Body_bold + type: Label.LabelType.Body wrapMode: Text.WordWrap } Item { @@ -89,7 +89,7 @@ Item { text: qsTr("I understand") onClicked: { - root.wizard.closeWizard(); + root.wizard.showClientParams(); } } Item { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml index 37880371..1e40b491 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml @@ -54,7 +54,7 @@ Item { } Button { Layout.alignment: Qt.AlignHCenter - Layout.preferredWidth: 320 + Layout.fillWidth: true colorScheme: root.colorScheme text: qsTr("Let's start") diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index d116c623..e7d176c8 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -69,23 +69,31 @@ Item { function closeWizard() { root.visible = false; } + function showOutlookSelector() { root.visible = true; + rootStackLayout.currentIndex = 0; leftContent.showOutlookSelector(); rightContent.currentIndex = 3; } + function start() { root.visible = true; + rootStackLayout.currentIndex = 0; leftContent.showOnboarding(); rightContent.currentIndex = 0; } + function startClientConfig() { root.visible = true; + rootStackLayout.currentIndex = 0; leftContent.showClientSelector(); rightContent.currentIndex = 2; } + function startLogin(wasSignedOut = false) { root.visible = true; + rootStackLayout.currentIndex = 0; root.userID = ""; root.address = ""; root.wasSignedOut = wasSignedOut; @@ -96,10 +104,17 @@ Item { function showClientWarning() { root.visible = true; + rootStackLayout.currentIndex = 0; leftContent.showClientConfigWarning(); rightContent.currentIndex = 4 } + function showClientParams() { + root.visible = true; + rootStackLayout.currentIndex = 1; + + } + Connections { function onLoginFinished() { startClientConfig(); @@ -107,114 +122,130 @@ Item { target: Backend } - RowLayout { + + StackLayout { + id: rootStackLayout anchors.fill: parent - spacing: 0 - Rectangle { - id: leftHalf + // rootStackLayout index 0 + RowLayout { Layout.fillHeight: true Layout.fillWidth: true - color: root.colorScheme.background_norm + spacing: 0 - LeftPane { - id: leftContent + Rectangle { + id: leftHalf + Layout.fillHeight: true + Layout.fillWidth: true + color: root.colorScheme.background_norm - anchors.bottom: parent.bottom - anchors.bottomMargin: 96 - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top - anchors.topMargin: 96 - clip: true - colorScheme: root.colorScheme - width: 444 - wizard: root - } - Image { - id: mailLogoWithWordmark - anchors.bottom: parent.bottom - anchors.bottomMargin: 48 - anchors.horizontalCenter: parent.horizontalCenter - fillMode: Image.PreserveAspectFit - height: 24 - mipmap: true - source: root.colorScheme.mail_logo_with_wordmark - } - } - Rectangle { - id: rightHalf - Layout.fillHeight: true - Layout.fillWidth: true - color: root.colorScheme.background_weak + LeftPane { + id: leftContent - StackLayout { - id: rightContent - anchors.bottom: parent.bottom - anchors.bottomMargin: 96 - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top - anchors.topMargin: 96 - clip: true - currentIndex: 0 - width: 444 - - // stack index 0 - Onboarding { - Layout.fillHeight: true - Layout.fillWidth: true + anchors.bottom: parent.bottom + anchors.bottomMargin: 96 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 96 + clip: true colorScheme: root.colorScheme - - onOnboardingAccepted: root.startLogin() + width: 444 + wizard: root } + Image { + id: mailLogoWithWordmark + anchors.bottom: parent.bottom + anchors.bottomMargin: 48 + anchors.horizontalCenter: parent.horizontalCenter + fillMode: Image.PreserveAspectFit + height: 24 + mipmap: true + source: root.colorScheme.mail_logo_with_wordmark + } + } + Rectangle { + id: rightHalf + Layout.fillHeight: true + Layout.fillWidth: true + color: root.colorScheme.background_weak - // stack index 1 - Login { - id: login - Layout.fillHeight: true - Layout.fillWidth: true - colorScheme: root.colorScheme + StackLayout { + id: rightContent + anchors.bottom: parent.bottom + anchors.bottomMargin: 96 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 96 + clip: true + currentIndex: 0 + width: 444 - onLoginAbort: { - root.closeWizard(); + // stack index 0 + Onboarding { + Layout.fillHeight: true + Layout.fillWidth: true + colorScheme: root.colorScheme + + onOnboardingAccepted: root.startLogin() + } + + // stack index 1 + Login { + id: login + Layout.fillHeight: true + Layout.fillWidth: true + colorScheme: root.colorScheme + + onLoginAbort: { + root.closeWizard(); + } + } + + // stack index 2 + ClientConfigSelector { + id: clientConfigSelector + Layout.fillHeight: true + Layout.fillWidth: true + wizard: root + } + // stack index 3 + ClientConfigOutlookSelector { + id: clientConfigOutlookSelector + Layout.fillHeight: true + Layout.fillWidth: true + wizard: root + } + // stack index 4 + ClientConfigWarning { + id: clientConfigWarning + Layout.fillHeight: true + Layout.fillWidth: true + wizard: root } } + LinkLabel { + id: reportProblemLink + anchors.bottom: parent.bottom + anchors.bottomMargin: 48 + anchors.horizontalCenter: parent.horizontalCenter + colorScheme: root.colorScheme + horizontalAlignment: Text.AlignRight + text: link("#", qsTr("Report problem")) + width: 444 - // stack index 2 - ClientConfigSelector { - id: clientConfigSelector - Layout.fillHeight: true - Layout.fillWidth: true - wizard: root - } - // stack index 3 - ClientConfigOutlookSelector { - id: clientConfigOutlookSelector - Layout.fillHeight: true - Layout.fillWidth: true - wizard: root - } - // stack index 4 - ClientConfigWarning { - id: clientConfigWarning - Layout.fillHeight: true - Layout.fillWidth: true - wizard: root + onLinkActivated: { + root.visible = false; + } } } - LinkLabel { - id: reportProblemLink - anchors.bottom: parent.bottom - anchors.bottomMargin: 48 - anchors.horizontalCenter: parent.horizontalCenter - colorScheme: root.colorScheme - horizontalAlignment: Text.AlignRight - text: link("#", qsTr("Report problem")) - width: 444 + } - onLinkActivated: { - root.visible = false; - } - } + // rootStackLayout index 1 + ClientConfigParameters { + id: clientConfigParameters + Layout.fillHeight: true + Layout.fillWidth: true + wizard: root } } } From ad31e6a9c534c37375b1df6e50c37c30af5236dd Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Thu, 10 Aug 2023 09:31:57 +0200 Subject: [PATCH 22/93] feat(GODT-2767): pass user and username to setup wizard. --- .../bridge-gui/bridge-gui/qml/MainWindow.qml | 2 +- .../SetupWizard/ClientConfigParameters.qml | 8 +++---- .../qml/SetupWizard/SetupWizard.qml | 23 +++++++++++-------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index 3e725f4d..7fb46a0e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -167,7 +167,7 @@ ApplicationWindow { Backend.quit(); } onShowSetupGuide: function (user, address) { - setupWizard.startClientConfig(); + setupWizard.startClientConfig(user, address); } onShowSetupWizard: { setupWizard.start(); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml index 426581fa..bcda3273 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml @@ -64,8 +64,6 @@ Rectangle { RowLayout { id: configuration - property string currentAddress: wizard.user ? wizard.user.address : "" - Layout.fillHeight: true Layout.fillWidth: true Layout.topMargin: 32 @@ -78,7 +76,7 @@ Rectangle { port: Backend.imapPort.toString() security: Backend.useSSLForIMAP ? "SSL" : "STARTTLS" title: qsTr("IMAP") - username: configuration.currentAddress + username: wizard.address } Configuration { Layout.fillWidth: true @@ -88,7 +86,7 @@ Rectangle { port: Backend.smtpPort.toString() security: Backend.useSSLForSMTP ? "SSL" : "STARTTLS" title: qsTr("SMTP") - username: configuration.currentAddress + username: wizard.address } } @@ -107,7 +105,7 @@ Rectangle { Layout.topMargin: 32 colorScheme: root.colorScheme text: qsTr("Done") - onClicked: root.wizard.closeWizard() + onClicked: wizard.closeWizard() } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index e7d176c8..7d9bf352 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -27,12 +27,11 @@ Item { Generic } - property string address property int client property string clientVersion property ColorScheme colorScheme - property string userID - property bool wasSignedOut + property var user + property string address function clientIconSource() { switch (client) { @@ -84,19 +83,19 @@ Item { rightContent.currentIndex = 0; } - function startClientConfig() { + function startClientConfig(user, address) { + root.user = user + root.address = address root.visible = true; rootStackLayout.currentIndex = 0; leftContent.showClientSelector(); rightContent.currentIndex = 2; } - function startLogin(wasSignedOut = false) { + function startLogin() { root.visible = true; rootStackLayout.currentIndex = 0; - root.userID = ""; root.address = ""; - root.wasSignedOut = wasSignedOut; leftContent.showLogin(); rightContent.currentIndex = 1; login.reset(true); @@ -116,8 +115,14 @@ Item { } Connections { - function onLoginFinished() { - startClientConfig(); + function onLoginFinished(userIndex, wasSignedOut) { + if (wasSignedOut) { + closeWizard(); + return; + } + let user = Backend.users.get(userIndex) + let address = user ? user.addresses[0] : "" + startClientConfig(user, address); } target: Backend From ca5f7ce9f605644682b7de8a5470fa4c37011eee Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Thu, 10 Aug 2023 15:01:32 +0200 Subject: [PATCH 23/93] feat(GODT-2767): connected existing entrypoints to wizard, and moved it to a stack layout. [skip-ci] --- .../bridge-gui/bridge-gui/Resources.qrc | 3 - .../bridge-gui/bridge-gui/qml/AccountView.qml | 10 +- .../bridge-gui/qml/ContentWrapper.qml | 57 +-- .../bridge-gui/bridge-gui/qml/MainWindow.qml | 116 +++-- .../bridge-gui/bridge-gui/qml/SetupGuide.qml | 293 ------------- .../qml/SetupWizard/SetupWizard.qml | 7 +- .../bridge-gui/bridge-gui/qml/SignIn.qml | 413 ------------------ .../bridge-gui/qml/WelcomeGuide.qml | 245 ----------- 8 files changed, 69 insertions(+), 1075 deletions(-) delete mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SetupGuide.qml delete mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SignIn.qml delete mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/WelcomeGuide.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index d3651c83..e2c6df5a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -106,7 +106,6 @@ qml/Resources/bug_report_flow.json qml/SettingsItem.qml qml/SettingsView.qml - qml/SetupGuide.qml qml/SetupWizard/ClientListItem.qml qml/SetupWizard/LeftPane.qml qml/SetupWizard/ClientConfigOutlookSelector.qml @@ -117,11 +116,9 @@ qml/SetupWizard/Login.qml qml/SetupWizard/Onboarding.qml qml/SetupWizard/StepDescriptionBox.qml - qml/SignIn.qml qml/ConnectionModeSettings.qml qml/SplashScreen.qml qml/Status.qml - qml/WelcomeGuide.qml qml/WebViewWindow.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml b/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml index ff196d43..f3a6b70a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml @@ -29,7 +29,7 @@ Item { property var user signal showSetupGuide(var user, string address) - signal showSignIn + signal showSignIn(var username) Rectangle { anchors.fill: parent @@ -92,9 +92,9 @@ Item { visible: root.user ? (root.user.state === EUserState.SignedOut) : false onClicked: { - if (!root.user) - return; - root.showSignIn(); + if (user) { + root.showSignIn(user.primaryEmailOrUsername()); + } } } Button { @@ -124,7 +124,7 @@ Item { showSeparator: splitMode.visible text: qsTr("Email clients") type: SettingsItem.Button - visible: _connected && (!root.user.splitMode) || (root.user.addresses.length === 1) + visible: _connected && ((!root.user.splitMode) || (root.user.addresses.length === 1)) onClicked: { if (!root.user) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml b/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml index 55e8ee51..ed24436e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml @@ -25,7 +25,8 @@ Item { signal closeWindow signal quitBridge signal showSetupGuide(var user, string address) - signal showSetupWizard + signal showSignIn(var username) + signal showSetupWizard() function selectUser(userID) { const users = Backend.users; @@ -50,10 +51,6 @@ Item { function showSettings() { rightContent.showGeneralSettings(); } - function showSignIn(username) { - signIn.username = username; - rightContent.showSignIn(); - } RowLayout { anchors.fill: parent @@ -234,8 +231,7 @@ Item { if (user.state !== EUserState.SignedOut) { rightContent.showAccount(); } else { - signIn.username = user.primaryEmailOrUsername(); - rightContent.showSignIn(); + showSignIn(user.primaryEmailOrUsername()); } } } @@ -283,8 +279,7 @@ Item { width: 36 onClicked: { - signIn.username = ""; - root.showSetupWizard(); + root.showSignIn("") } } } @@ -324,10 +319,6 @@ Item { function showPortSettings() { rightContent.currentIndex = 4; } - function showSignIn() { - rightContent.currentIndex = 1; - signIn.focus = true; - } anchors.fill: parent @@ -346,42 +337,14 @@ Item { onShowSetupGuide: function (user, address) { root.showSetupGuide(user, address); } - onShowSignIn: { - const user = this.user; - signIn.username = user ? user.primaryEmailOrUsername() : ""; - rightContent.showSignIn(); + onShowSignIn: function (username) { + root.showSignIn(username) } } - GridLayout { - // 1 Sign In - columns: 2 - - Button { - id: backButton - Layout.alignment: Qt.AlignTop - Layout.leftMargin: 18 - Layout.topMargin: 10 - colorScheme: root.colorScheme - horizontalPadding: 8 - icon.source: "/qml/icons/ic-arrow-left.svg" - secondary: true - - onClicked: { - signIn.abort(); - rightContent.showAccount(); - } - } - SignIn { - id: signIn - Layout.bottomMargin: 68 - Layout.fillHeight: true - Layout.fillWidth: true - Layout.leftMargin: 80 - backButton.width - 18 - Layout.preferredWidth: 320 - Layout.rightMargin: 80 - Layout.topMargin: 68 - colorScheme: root.colorScheme - } + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true + color: "#ff9900" } GeneralSettings { // 2 diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index 7fb46a0e..309f6906 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -46,21 +46,38 @@ ApplicationWindow { contentWrapper.showSettings(); } function showSetup(user, address) { - setupGuide.user = user; - setupGuide.address = address; - setupGuide.reset(); - contentLayout._showSetup = !!setupGuide.user; + contentLayout.currentIndex = 1; + setupWizard.startClientCOnfig(user, address) } function showSignIn(username) { - if (contentLayout.currentIndex === 1) - return; - contentWrapper.showSignIn(username); + contentLayout.currentIndex = 1; + setupWizard.startLogin(username) } + function showWebViewOverlay(url) { webViewOverlay.visible = true; webViewOverlay.url = url; } + function layoutForUserCount(userCount) { + if (userCount === 0) { + showSignIn(""); + return; + } + + const u = Backend.users.get(0); + if (!u) { + console.trace(); + console.log("empty user"); + setupWizard.start(); + return; + } + + if ((userCount === 1) && (u.state === EUserState.SignedOut)) { + setupWizard.startLogin(u.primaryEmailOrUsername()); + } + } + colorScheme: ProtonStyle.currentStyle height: _defaultHeight minimumWidth: _defaultWidth @@ -72,10 +89,8 @@ ApplicationWindow { function onRowsAboutToBeRemoved(parent, first, last) { for (let i = first; i <= last; i++) { const user = Backend.users.get(i); - if (setupGuide.user === user) { - setupGuide.user = null; - contentLayout._showSetup = false; - return; + if (setupWizard.user === user) { + setupWizard.closeWizard(); } } } @@ -94,13 +109,6 @@ ApplicationWindow { target: Backend.users } Connections { - function onLoginFinished(index, wasSignedOut) { - // const user = Backend.users.get(index); - // if (user && !wasSignedOut) { - // root.showSetup(user, user.addresses[0]); - // } - // console.debug("Login finished", index); - } function onSelectUser(userID, forceShowWindow) { contentWrapper.selectUser(userID); if (forceShowWindow) { @@ -121,33 +129,19 @@ ApplicationWindow { target: Backend } + + Connections { + function onCountChanged(count) { + layoutForUserCount(count) + } + + target: Backend.users + } StackLayout { id: contentLayout - property bool _showSetup: false - anchors.fill: parent - currentIndex: { - // show welcome when there are no users - if (Backend.users.count === 0) { - setupWizard.start(); - return 0; - } - const u = Backend.users.get(0); - if (!u) { - console.trace(); - console.log("empty user"); - return 1; - } - if ((Backend.users.count === 1) && (u.state === EUserState.SignedOut)) { - showSignIn(u.primaryEmailOrUsername()); - return 0; - } - if (contentLayout._showSetup) { - return 2; - } - return 0; - } + currentIndex: 0 ContentWrapper { // 0 @@ -169,33 +163,23 @@ ApplicationWindow { onShowSetupGuide: function (user, address) { setupWizard.startClientConfig(user, address); } - onShowSetupWizard: { - setupWizard.start(); + onShowSignIn: function(username) { + root.showSignIn(username) } } - WelcomeGuide { - Layout.fillHeight: true - Layout.fillWidth: true // 1 - colorScheme: root.colorScheme - } - SetupGuide { - // 2 - id: setupGuide - Layout.fillHeight: true - Layout.fillWidth: true + + SetupWizard { + id: setupWizard + Layout.fillWidth: true; + Layout.fillHeight: true; colorScheme: root.colorScheme - onDismissed: { - root.showSetup(null, ""); - } - onFinished: { - // TODO: Do not close window. Trigger Backend to check that - // there is a successfully connected client. Then Backend - // should send another signal to close the setup guide. - root.showSetup(null, ""); + onWizardEnded: { + contentLayout.currentIndex = 0 } } } + WebView { id: webViewOverlay anchors.fill: parent @@ -204,12 +188,6 @@ ApplicationWindow { url: "" visible: false } - SetupWizard { - id: setupWizard - anchors.fill: parent - colorScheme: root.colorScheme - visible: false - } NotificationPopups { colorScheme: root.colorScheme mainWindow: root @@ -219,4 +197,8 @@ ApplicationWindow { id: splashScreen colorScheme: root.colorScheme } + + Component.onCompleted: { + layoutForUserCount(Backend.users.count) + } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupGuide.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupGuide.qml deleted file mode 100644 index 4cdfe43f..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupGuide.qml +++ /dev/null @@ -1,293 +0,0 @@ -// Copyright (c) 2023 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 . -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import QtQuick.Controls.impl -import Proton - -Item { - id: root - - property string address - property ColorScheme colorScheme - property var user - - signal dismissed - signal finished - - function reset() { - guidePages.currentIndex = 0; - clientList.currentIndex = -1; - actionList.currentIndex = -1; - } - function setupAction(actionID, clientID) { - if (user) { - user.setupGuideSeen = true; - } - switch (actionID) { - case -1: - root.dismissed(); - break; // dismiss - case 0 // automatic - : - if (user) { - switch (clientID) { - case 0: - root.user.configureAppleMail(root.address); - Backend.notifyAutoconfigClicked("AppleMail"); - break; - } - } - root.finished(); - break; - case 1 // manual - : - let clientObj = clients.get(clientID); - if (clientObj !== undefined && clientObj.link !== "") { - Qt.openUrlExternally(clientObj.link); - Backend.notifyKBArticleClicked(clientObj.link); - } else { - console.log("unexpected client index", actionID, clientID); - } - root.finished(); - break; - default: - console.log("unexpected client setup action", actionID, clientID); - } - } - - implicitHeight: children[0].implicitHeight - implicitWidth: children[0].implicitWidth - - ListModel { - id: clients - - property bool haveAutoSetup: true - property string iconSource: "/qml/icons/ic-apple-mail.svg" - property string link: "https://proton.me/support/protonmail-bridge-clients-apple-mail" - property string name: "Apple Mail" - - Component.onCompleted: { - if (Backend.goos === "darwin") { - append({ - "name": "Apple Mail", - "iconSource": "/qml/icons/ic-apple-mail.svg", - "haveAutoSetup": true, - "link": "https://proton.me/support/protonmail-bridge-clients-apple-mail" - }); - append({ - "name": "Microsoft Outlook", - "iconSource": "/qml/icons/ic-microsoft-outlook.svg", - "haveAutoSetup": false, - "link": "https://proton.me/support/protonmail-bridge-clients-macos-outlook-2019" - }); - } - if (Backend.goos === "windows") { - append({ - "name": "Microsoft Outlook", - "iconSource": "/qml/icons/ic-microsoft-outlook.svg", - "haveAutoSetup": false, - "link": "https://proton.me/support/protonmail-bridge-clients-windows-outlook-2019" - }); - } - append({ - "name": "Mozilla Thunderbird", - "iconSource": "/qml/icons/ic-mozilla-thunderbird.svg", - "haveAutoSetup": false, - "link": "https://proton.me/support/protonmail-bridge-clients-windows-thunderbird" - }); - append({ - "name": "Other", - "iconSource": "/qml/icons/ic-other-mail-clients.svg", - "haveAutoSetup": false, - "link": "https://proton.me/support/protonmail-bridge-configure-client" - }); - } - } - Rectangle { - anchors.fill: root - color: root.colorScheme.background_norm - } - StackLayout { - id: guidePages - anchors.bottomMargin: 70 - anchors.fill: parent - anchors.leftMargin: 80 - anchors.rightMargin: 80 - anchors.topMargin: 30 - - ColumnLayout { - // 0: Client selection - id: clientView - - property int columnWidth: 268 - - Layout.fillHeight: true - spacing: 8 - - Label { - colorScheme: root.colorScheme - text: qsTr("Setting up email client") - type: Label.LabelType.Heading - } - Label { - color: root.colorScheme.text_weak - colorScheme: root.colorScheme - text: address - type: Label.LabelType.Lead - } - RowLayout { - Layout.topMargin: 32 - clientView.spacing - spacing: 24 - - ColumnLayout { - id: clientColumn - Layout.alignment: Qt.AlignTop - - Label { - id: labelA - colorScheme: root.colorScheme - text: qsTr("Choose an email client") - type: Label.LabelType.Body_semibold - } - ListView { - id: clientList - Layout.fillHeight: true - model: clients - width: clientView.columnWidth - - delegate: Item { - implicitHeight: clientRow.height - implicitWidth: clientRow.width - - ColumnLayout { - id: clientRow - width: clientList.width - - RowLayout { - Layout.bottomMargin: 12 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.topMargin: 12 - - ColorImage { - height: 36 - source: model.iconSource - sourceSize.height: 36 - } - Label { - Layout.leftMargin: 12 - colorScheme: root.colorScheme - text: model.name - type: Label.LabelType.Body - } - } - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: root.colorScheme.border_weak - } - } - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - - onClicked: { - clientList.currentIndex = index; - if (!model.haveAutoSetup) { - root.setupAction(1, index); - } - } - } - } - highlight: Rectangle { - color: root.colorScheme.interaction_default_active - radius: ProtonStyle.context_item_radius - } - } - } - ColumnLayout { - id: actionColumn - Layout.alignment: Qt.AlignTop - visible: clientList.currentIndex >= 0 && clients.get(clientList.currentIndex).haveAutoSetup - - Label { - colorScheme: root.colorScheme - text: qsTr("Choose configuration mode") - type: Label.LabelType.Body_semibold - } - ListView { - id: actionList - Layout.fillHeight: true - model: [qsTr("Configure automatically"), qsTr("Configure manually")] - width: clientView.columnWidth - - delegate: Item { - implicitHeight: children[0].height - implicitWidth: children[0].width - - ColumnLayout { - width: actionList.width - - Label { - Layout.bottomMargin: 20 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.topMargin: 20 - colorScheme: root.colorScheme - text: modelData - type: Label.LabelType.Body - } - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: root.colorScheme.border_weak - } - } - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - - onClicked: { - actionList.currentIndex = index; - root.setupAction(index, clientList.currentIndex); - } - } - } - highlight: Rectangle { - color: root.colorScheme.interaction_default_active - radius: ProtonStyle.context_item_radius - } - } - } - } - Item { - Layout.fillHeight: true - } - Button { - colorScheme: root.colorScheme - flat: true - text: qsTr("Set up later") - - onClicked: { - root.setupAction(-1, -1); - if (user) { - user.setupGuideSeen = true; - } - root.dismissed(); - } - } - } - } -} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 7d9bf352..36391627 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -33,6 +33,8 @@ Item { property var user property string address + signal wizardEnded() + function clientIconSource() { switch (client) { case SetupWizard.Client.AppleMail: @@ -66,7 +68,7 @@ Item { } function closeWizard() { - root.visible = false; + wizardEnded() } function showOutlookSelector() { @@ -92,13 +94,14 @@ Item { rightContent.currentIndex = 2; } - function startLogin() { + function startLogin(username = "") { root.visible = true; rootStackLayout.currentIndex = 0; root.address = ""; leftContent.showLogin(); rightContent.currentIndex = 1; login.reset(true); + login.username = username; } function showClientWarning() { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SignIn.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SignIn.qml deleted file mode 100644 index 35278df0..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SignIn.qml +++ /dev/null @@ -1,413 +0,0 @@ -// Copyright (c) 2023 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 . -import QtQml -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import QtQuick.Controls.impl -import Proton - -FocusScope { - id: root - - property ColorScheme colorScheme - property alias currentIndex: stackLayout.currentIndex - property alias username: usernameTextField.text - - function abort() { - root.reset(); - Backend.loginAbort(usernameTextField.text); - } - function reset() { - stackLayout.currentIndex = 0; - loginNormalLayout.reset(); - login2FALayout.reset(); - login2PasswordLayout.reset(); - } - - implicitHeight: children[0].implicitHeight - implicitWidth: children[0].implicitWidth - state: "Page 1" - - states: [ - State { - name: "Page 1" - - PropertyChanges { - currentIndex: 0 - target: stackLayout - } - }, - State { - name: "Page 2" - - PropertyChanges { - currentIndex: 1 - target: stackLayout - } - }, - State { - name: "Page 3" - - PropertyChanges { - currentIndex: 2 - target: stackLayout - } - } - ] - - StackLayout { - id: stackLayout - function loginFailed() { - signInButton.loading = false; - usernameTextField.enabled = true; - usernameTextField.error = true; - passwordTextField.enabled = true; - passwordTextField.error = true; - } - - anchors.fill: parent - - Connections { - function onLogin2FAError(_) { - console.assert(stackLayout.currentIndex === 1, "Unexpected login2FAError"); - twoFAButton.loading = false; - twoFactorPasswordTextField.enabled = true; - twoFactorPasswordTextField.error = true; - twoFactorPasswordTextField.errorString = qsTr("Your code is incorrect"); - twoFactorPasswordTextField.focus = true; - } - function onLogin2FAErrorAbort(_) { - console.assert(stackLayout.currentIndex === 1, "Unexpected login2FAErrorAbort"); - root.reset(); - errorLabel.text = qsTr("Incorrect login credentials. Please try again."); - } - function onLogin2FARequested(username) { - console.assert(stackLayout.currentIndex === 0, "Unexpected login2FARequested"); - twoFactorUsernameLabel.text = username; - stackLayout.currentIndex = 1; - twoFactorPasswordTextField.focus = true; - } - function onLogin2PasswordError(_) { - console.assert(stackLayout.currentIndex === 2, "Unexpected login2PasswordError"); - secondPasswordButton.loading = false; - secondPasswordTextField.enabled = true; - secondPasswordTextField.error = true; - secondPasswordTextField.errorString = qsTr("Your mailbox password is incorrect"); - secondPasswordTextField.focus = true; - } - function onLogin2PasswordErrorAbort(_) { - console.assert(stackLayout.currentIndex === 2, "Unexpected login2PasswordErrorAbort"); - root.reset(); - errorLabel.text = qsTr("Incorrect login credentials. Please try again."); - } - function onLogin2PasswordRequested() { - console.assert(stackLayout.currentIndex === 0 || stackLayout.currentIndex === 1, "Unexpected login2PasswordRequested"); - stackLayout.currentIndex = 2; - secondPasswordTextField.focus = true; - } - function onLoginAlreadyLoggedIn(_) { - stackLayout.currentIndex = 0; - root.reset(); - } - function onLoginConnectionError(_) { - if (stackLayout.currentIndex === 0) { - stackLayout.loginFailed(); - } - } - function onLoginFinished(_) { - stackLayout.currentIndex = 0; - root.reset(); - } - function onLoginFreeUserError() { - console.assert(stackLayout.currentIndex === 0, "Unexpected loginFreeUserError"); - stackLayout.loginFailed(); - } - function onLoginUsernamePasswordError(errorMsg) { - console.assert(stackLayout.currentIndex === 0, "Unexpected loginUsernamePasswordError"); - stackLayout.loginFailed(); - if (errorMsg !== "") - errorLabel.text = errorMsg; - else - errorLabel.text = qsTr("Incorrect login credentials"); - } - - target: Backend - } - ColumnLayout { - id: loginNormalLayout - function reset() { - signInButton.loading = false; - errorLabel.text = ""; - usernameTextField.enabled = true; - usernameTextField.error = false; - usernameTextField.errorString = ""; - usernameTextField.focus = true; - passwordTextField.enabled = true; - passwordTextField.error = false; - passwordTextField.errorString = ""; - passwordTextField.text = ""; - } - - spacing: 0 - - Label { - Layout.alignment: Qt.AlignHCenter - Layout.topMargin: 16 - colorScheme: root.colorScheme - text: qsTr("Sign in") - type: Label.LabelType.Title - } - Label { - id: subTitle - Layout.alignment: Qt.AlignHCenter - Layout.topMargin: 8 - color: root.colorScheme.text_weak - colorScheme: root.colorScheme - text: qsTr("Enter your Proton Account details.") - type: Label.LabelType.Body - } - RowLayout { - Layout.fillWidth: true - Layout.topMargin: 36 - spacing: 0 - visible: errorLabel.text.length > 0 - - ColorImage { - color: root.colorScheme.signal_danger - height: errorLabel.lineHeight - source: "/qml/icons/ic-exclamation-circle-filled.svg" - sourceSize.height: errorLabel.lineHeight - } - Label { - id: errorLabel - Layout.fillWidth: true - Layout.leftMargin: 4 - color: root.colorScheme.signal_danger - colorScheme: root.colorScheme - type: root.error ? Label.LabelType.Caption_semibold : Label.LabelType.Caption - wrapMode: Text.WordWrap - } - } - TextField { - id: usernameTextField - Layout.fillWidth: true - Layout.topMargin: 24 - colorScheme: root.colorScheme - focus: true - label: qsTr("Email or username") - validateOnEditingFinished: false - validator: function (str) { - if (str.length === 0) { - return qsTr("Enter email or username"); - } - } - - onAccepted: passwordTextField.forceActiveFocus() - onTextChanged: { - // remove "invalid username / password error" - if (error || errorLabel.text.length > 0) { - errorLabel.text = ""; - usernameTextField.error = false; - passwordTextField.error = false; - } - } - } - TextField { - id: passwordTextField - Layout.fillWidth: true - Layout.topMargin: 8 - colorScheme: root.colorScheme - echoMode: TextInput.Password - label: qsTr("Password") - validateOnEditingFinished: false - validator: function (str) { - if (str.length === 0) { - return qsTr("Enter password"); - } - } - - onAccepted: signInButton.checkAndSignIn() - onTextChanged: { - // remove "invalid username / password error" - if (error || errorLabel.text.length > 0) { - errorLabel.text = ""; - usernameTextField.error = false; - passwordTextField.error = false; - } - } - } - Button { - id: signInButton - function checkAndSignIn() { - usernameTextField.validate(); - passwordTextField.validate(); - if (usernameTextField.error || passwordTextField.error) { - return; - } - usernameTextField.enabled = false; - passwordTextField.enabled = false; - loading = true; - Backend.login(usernameTextField.text, Qt.btoa(passwordTextField.text)); - } - - Layout.fillWidth: true - Layout.topMargin: 24 - colorScheme: root.colorScheme - enabled: !loading - text: loading ? qsTr("Signing in") : qsTr("Sign in") - - onClicked: { - checkAndSignIn(); - } - } - Label { - Layout.alignment: Qt.AlignHCenter - Layout.topMargin: 24 - colorScheme: root.colorScheme - text: link("https://proton.me/mail/pricing", qsTr("Create or upgrade your account")) - textFormat: Text.StyledText - type: Label.LabelType.Body - - onLinkActivated: { - Qt.openUrlExternally(link); - } - } - } - ColumnLayout { - id: login2FALayout - function reset() { - twoFAButton.loading = false; - twoFactorPasswordTextField.enabled = true; - twoFactorPasswordTextField.error = false; - twoFactorPasswordTextField.errorString = ""; - twoFactorPasswordTextField.text = ""; - } - - spacing: 0 - - Label { - Layout.alignment: Qt.AlignCenter - Layout.topMargin: 16 - colorScheme: root.colorScheme - text: qsTr("Two-factor authentication") - type: Label.LabelType.Heading - } - Label { - id: twoFactorUsernameLabel - Layout.alignment: Qt.AlignCenter - Layout.topMargin: 8 - color: root.colorScheme.text_weak - colorScheme: root.colorScheme - type: Label.LabelType.Lead - } - TextField { - id: twoFactorPasswordTextField - Layout.fillWidth: true - Layout.topMargin: 32 - assistiveText: qsTr("Enter the 6-digit code") - colorScheme: root.colorScheme - label: qsTr("Two-factor code") - validateOnEditingFinished: false - validator: function (str) { - if (str.length === 0) { - return qsTr("Enter the 6-digit code"); - } - } - - onAccepted: { - twoFAButton.onClicked(); - } - onTextChanged: { - if (text.length >= 6) { - twoFAButton.onClicked(); - } - } - } - Button { - id: twoFAButton - Layout.fillWidth: true - Layout.topMargin: 24 - colorScheme: root.colorScheme - enabled: !loading - text: loading ? qsTr("Authenticating") : qsTr("Authenticate") - - onClicked: { - twoFactorPasswordTextField.validate(); - if (twoFactorPasswordTextField.error) { - return; - } - twoFactorPasswordTextField.enabled = false; - loading = true; - Backend.login2FA(usernameTextField.text, Qt.btoa(twoFactorPasswordTextField.text)); - } - } - } - ColumnLayout { - id: login2PasswordLayout - function reset() { - secondPasswordButton.loading = false; - secondPasswordTextField.enabled = true; - secondPasswordTextField.error = false; - secondPasswordTextField.errorString = ""; - secondPasswordTextField.text = ""; - } - - spacing: 0 - - Label { - Layout.alignment: Qt.AlignCenter - Layout.topMargin: 16 - colorScheme: root.colorScheme - text: qsTr("Unlock your mailbox") - type: Label.LabelType.Heading - } - TextField { - id: secondPasswordTextField - Layout.fillWidth: true - Layout.topMargin: 8 + implicitHeight + 24 + subTitle.implicitHeight - colorScheme: root.colorScheme - echoMode: TextInput.Password - label: qsTr("Mailbox password") - validateOnEditingFinished: false - validator: function (str) { - if (str.length === 0) { - return qsTr("Enter password"); - } - } - - onAccepted: { - secondPasswordButton.onClicked(); - } - } - Button { - id: secondPasswordButton - Layout.fillWidth: true - Layout.topMargin: 24 - colorScheme: root.colorScheme - enabled: !loading - text: loading ? qsTr("Unlocking") : qsTr("Unlock") - - onClicked: { - secondPasswordTextField.validate(); - if (secondPasswordTextField.error) { - return; - } - secondPasswordTextField.enabled = false; - loading = true; - Backend.login2Password(usernameTextField.text, Qt.btoa(secondPasswordTextField.text)); - } - } - } - } -} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/WelcomeGuide.qml b/internal/frontend/bridge-gui/bridge-gui/qml/WelcomeGuide.qml deleted file mode 100644 index 94df5dec..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/WelcomeGuide.qml +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright (c) 2023 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 . -import QtQml -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import Proton - -Item { - id: root - - property ColorScheme colorScheme - - implicitHeight: children[0].implicitHeight - implicitWidth: children[0].implicitWidth - - RowLayout { - anchors.fill: parent - spacing: 0 - - states: [ - State { - name: "Page 1" - - PropertyChanges { - currentIndex: 0 - target: signInItem - } - }, - State { - name: "Page 2" - - PropertyChanges { - currentIndex: 1 - target: signInItem - } - }, - State { - name: "Page 3" - - PropertyChanges { - currentIndex: 2 - target: signInItem - } - } - ] - - Rectangle { - Layout.fillHeight: true - Layout.fillWidth: true - color: root.colorScheme.background_norm - implicitHeight: children[0].implicitHeight - implicitWidth: children[0].implicitWidth - visible: signInItem.currentIndex === 0 - - GridLayout { - anchors.fill: parent - columnSpacing: 0 - columns: 3 - rowSpacing: 0 - - // top margin - Item { - Layout.columnSpan: 3 - Layout.fillWidth: true - - // Using binding component here instead of direct binding to avoid binding loop during construction of element - Binding on Layout.preferredHeight { - value: (parent.height - welcomeContentItem.height) / 4 - } - } - - // left margin - Item { - Layout.fillWidth: true - Layout.maximumWidth: 80 - Layout.minimumWidth: 48 - Layout.preferredHeight: welcomeContentItem.height - } - ColumnLayout { - id: welcomeContentItem - Layout.fillWidth: true - spacing: 0 - - Image { - Layout.alignment: Qt.AlignHCenter - Layout.topMargin: 16 - source: "/qml/icons/img-welcome.svg" - sourceSize.height: 148 - sourceSize.width: 264 - } - Label { - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - Layout.topMargin: 16 - colorScheme: root.colorScheme - horizontalAlignment: Text.AlignHCenter - text: qsTr("Welcome to\nProton Mail Bridge") - type: Label.LabelType.Heading - } - Label { - id: longTextLabel - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - Layout.preferredWidth: 320 - Layout.topMargin: 16 - colorScheme: root.colorScheme - horizontalAlignment: Text.AlignHCenter - text: qsTr("Add your Proton Mail account to securely access and manage your messages in your favorite email client. Bridge runs in the background and encrypts and decrypts your messages seamlessly.") - type: Label.LabelType.Body - wrapMode: Text.WordWrap - } - } - - // Right margin - Item { - Layout.fillWidth: true - Layout.maximumWidth: 80 - Layout.minimumWidth: 48 - Layout.preferredHeight: welcomeContentItem.height - } - - // bottom margin - Item { - Layout.columnSpan: 3 - Layout.fillHeight: true - Layout.fillWidth: true - implicitHeight: children[0].implicitHeight + children[0].anchors.bottomMargin + children[0].anchors.topMargin - implicitWidth: children[0].implicitWidth - - Image { - id: logoImage - anchors.bottom: parent.bottom - anchors.bottomMargin: 48 - anchors.horizontalCenter: parent.horizontalCenter - anchors.topMargin: 48 - source: colorScheme.logo_img - sourceSize.height: 25 - sourceSize.width: 200 - } - } - } - } - Rectangle { - Layout.fillHeight: true - Layout.fillWidth: true - color: (signInItem.currentIndex == 0) ? root.colorScheme.background_weak : root.colorScheme.background_norm - implicitHeight: children[0].implicitHeight - implicitWidth: children[0].implicitWidth - - RowLayout { - anchors.fill: parent - spacing: 0 - - Item { - Layout.fillHeight: true - Layout.fillWidth: true - Layout.preferredWidth: signInItem.currentIndex == 0 ? 0 : parent.width / 4 - implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin - implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin - - Button { - anchors.bottom: parent.bottom - anchors.bottomMargin: 80 - anchors.left: parent.left - anchors.leftMargin: 80 - anchors.rightMargin: 80 - anchors.topMargin: 80 - colorScheme: root.colorScheme - secondary: true - text: qsTr("Back") - visible: signInItem.currentIndex != 0 - - onClicked: { - signInItem.abort(); - } - } - } - GridLayout { - Layout.fillHeight: true - Layout.fillWidth: true - columnSpacing: 0 - columns: 3 - rowSpacing: 0 - - // top margin - Item { - Layout.columnSpan: 3 - Layout.fillWidth: true - - // Using binding component here instead of direct binding to avoid binding loop during construction of element - Binding on Layout.preferredHeight { - value: (parent.height - signInItem.height) / 4 - } - } - - // left margin - Item { - Layout.fillWidth: true - Layout.maximumWidth: 80 - Layout.minimumWidth: 48 - Layout.preferredHeight: signInItem.height - } - SignIn { - id: signInItem - Layout.fillWidth: true - Layout.preferredWidth: 320 - colorScheme: root.colorScheme - focus: true - username: Backend.users.count === 1 && Backend.users.get(0) && (Backend.users.get(0).state === EUserState.SignedOut) ? Backend.users.get(0).username : "" - } - - // Right margin - Item { - Layout.fillWidth: true - Layout.maximumWidth: 80 - Layout.minimumWidth: 48 - Layout.preferredHeight: signInItem.height - } - - // bottom margin - Item { - Layout.columnSpan: 3 - Layout.fillHeight: true - Layout.fillWidth: true - } - } - Item { - Layout.fillHeight: true - Layout.preferredWidth: signInItem.currentIndex === 0 ? 0 : parent.width / 4 - } - } - } - } -} From bb5a91ee6df8a63f58d40f6238663eb9a6f7d613 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Fri, 11 Aug 2023 09:29:00 +0200 Subject: [PATCH 24/93] feat(GODT-2767): wired bug report link + use enum for wizard stack layout. --- .../bridge-gui/qml/ContentWrapper.qml | 4 ++ .../bridge-gui/bridge-gui/qml/MainWindow.qml | 7 ++- .../SetupWizard/ClientConfigParameters.qml | 1 + .../qml/SetupWizard/SetupWizard.qml | 49 +++++++++++-------- 4 files changed, 39 insertions(+), 22 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml b/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml index ed24436e..6f2ed1f8 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml @@ -42,6 +42,9 @@ Item { } console.error("User with ID ", userID, " was not found in the account list"); } + function showBugReport() { + rightContent.showBugReport(); + } function showHelp() { rightContent.showHelpView(); } @@ -52,6 +55,7 @@ Item { rightContent.showGeneralSettings(); } + RowLayout { anchors.fill: parent spacing: 0 diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index 309f6906..976229e0 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -47,7 +47,7 @@ ApplicationWindow { } function showSetup(user, address) { contentLayout.currentIndex = 1; - setupWizard.startClientCOnfig(user, address) + setupWizard.startClientConfig(user, address) } function showSignIn(username) { contentLayout.currentIndex = 1; @@ -161,7 +161,7 @@ ApplicationWindow { Backend.quit(); } onShowSetupGuide: function (user, address) { - setupWizard.startClientConfig(user, address); + root.showSetup(user, address); } onShowSignIn: function(username) { root.showSignIn(username) @@ -177,6 +177,9 @@ ApplicationWindow { onWizardEnded: { contentLayout.currentIndex = 0 } + onShowBugReport: { + contentWrapper.showBugReport(); + } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml index bcda3273..2c7de38b 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml @@ -120,6 +120,7 @@ Rectangle { onLinkActivated: { wizard.closeWizard(); + wizard.showBugReport(); } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 36391627..2e12f73f 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -27,6 +27,19 @@ Item { Generic } + enum RootStack { + TwoPanesView = 0, + ClientConfigParameters = 1 + } + + enum ContentStack { + Onboarding = 0, + Login = 1, + ClientConfigSelector = 2, + ClientConfigOutlookSelector = 3, + ClientConfigWarning = 4 + } + property int client property string clientVersion property ColorScheme colorScheme @@ -34,6 +47,7 @@ Item { property string address signal wizardEnded() + signal showBugReport() function clientIconSource() { switch (client) { @@ -72,48 +86,42 @@ Item { } function showOutlookSelector() { - root.visible = true; - rootStackLayout.currentIndex = 0; + rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; leftContent.showOutlookSelector(); - rightContent.currentIndex = 3; + rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigOutlookSelector; } function start() { - root.visible = true; - rootStackLayout.currentIndex = 0; + rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; leftContent.showOnboarding(); - rightContent.currentIndex = 0; + rightContent.currentIndex = SetupWizard.ContentStack.Onboarding; } function startClientConfig(user, address) { root.user = user root.address = address - root.visible = true; - rootStackLayout.currentIndex = 0; + rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; leftContent.showClientSelector(); - rightContent.currentIndex = 2; + rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigSelector; } function startLogin(username = "") { - root.visible = true; - rootStackLayout.currentIndex = 0; + rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; root.address = ""; leftContent.showLogin(); - rightContent.currentIndex = 1; + rightContent.currentIndex = SetupWizard.ContentStack.Login; login.reset(true); login.username = username; } function showClientWarning() { - root.visible = true; - rootStackLayout.currentIndex = 0; + rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; leftContent.showClientConfigWarning(); - rightContent.currentIndex = 4 + rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigWarning } function showClientParams() { - root.visible = true; - rootStackLayout.currentIndex = 1; + rootStackLayout.currentIndex = SetupWizard.RootStack.ClientConfigParameters; } @@ -135,7 +143,7 @@ Item { id: rootStackLayout anchors.fill: parent - // rootStackLayout index 0 + // rootStackLayout index 0 SetupWizard.RootStack.TwoPanesView RowLayout { Layout.fillHeight: true Layout.fillWidth: true @@ -242,13 +250,14 @@ Item { width: 444 onLinkActivated: { - root.visible = false; + closeWizard(); + showBugReport(); } } } } - // rootStackLayout index 1 + // rootStackLayout index 1 SetupWizard.RootStack.ClientConfigParameters ClientConfigParameters { id: clientConfigParameters Layout.fillHeight: true From 7355c7dfd6556dd36ec710566a7d82c33ecc6b2e Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Fri, 11 Aug 2023 09:52:29 +0200 Subject: [PATCH 25/93] feat(GODT-2767): unified colorScheme management. [skip-ci] --- .../bridge-gui/bridge-gui/qml/MainWindow.qml | 8 ++-- .../bridge-gui/qml/Proton/Button.qml | 2 +- .../ClientConfigOutlookSelector.qml | 11 ++--- .../SetupWizard/ClientConfigParameters.qml | 15 +++--- .../qml/SetupWizard/ClientConfigSelector.qml | 13 +++--- .../qml/SetupWizard/ClientConfigWarning.qml | 11 ++--- .../bridge-gui/qml/SetupWizard/LeftPane.qml | 11 ++--- .../bridge-gui/qml/SetupWizard/Login.qml | 46 +++++++++---------- .../bridge-gui/qml/SetupWizard/Onboarding.qml | 15 +++--- .../qml/SetupWizard/SetupWizard.qml | 29 ++++++------ .../qml/SetupWizard/StepDescriptionBox.qml | 1 - 11 files changed, 75 insertions(+), 87 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index 976229e0..5aba8680 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -47,11 +47,11 @@ ApplicationWindow { } function showSetup(user, address) { contentLayout.currentIndex = 1; - setupWizard.startClientConfig(user, address) + setupWizard.showClientConfig(user, address) } function showSignIn(username) { contentLayout.currentIndex = 1; - setupWizard.startLogin(username) + setupWizard.showLogin(username) } function showWebViewOverlay(url) { @@ -69,12 +69,12 @@ ApplicationWindow { if (!u) { console.trace(); console.log("empty user"); - setupWizard.start(); + setupWizard.showOnboarding(); return; } if ((userCount === 1) && (u.state === EUserState.SignedOut)) { - setupWizard.startLogin(u.primaryEmailOrUsername()); + setupWizard.showLogin(u.primaryEmailOrUsername()); } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml index af085093..dd844c94 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml @@ -128,7 +128,7 @@ T.Button { return control.colorScheme.text_norm; } } - colorScheme: root.colorScheme + colorScheme: control.colorScheme elide: Text.ElideRight horizontalAlignment: Qt.AlignHCenter opacity: control.enabled || control.loading ? 1.0 : 0.5 diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigOutlookSelector.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigOutlookSelector.qml index 5843eb5a..180532eb 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigOutlookSelector.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigOutlookSelector.qml @@ -20,7 +20,6 @@ import Proton Item { id: root - property ColorScheme colorScheme: wizard.colorScheme readonly property bool onMacOS: (Backend.goos === "darwin") readonly property bool onWindows: (Backend.goos === "windows") property var wizard @@ -34,7 +33,7 @@ Item { Label { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme text: qsTr("Pick your version of Outlook") type: Label.LabelType.Heading } @@ -43,7 +42,7 @@ Item { } ClientListItem { Layout.fillWidth: true - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme iconSource: "/qml/icons/ic-microsoft-outlook.svg" text: "Outlook from Microsoft 365" @@ -54,7 +53,7 @@ Item { } ClientListItem { Layout.fillWidth: true - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme iconSource: "/qml/icons/ic-microsoft-outlook.svg" text: "Outlook 2019" @@ -65,7 +64,7 @@ Item { } ClientListItem { Layout.fillWidth: true - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme iconSource: "/qml/icons/ic-microsoft-outlook.svg" text: "Outlook 2016" @@ -79,7 +78,7 @@ Item { } Button { Layout.fillWidth: true - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme secondary: true text: qsTr("Cancel") diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml index 2c7de38b..224e8b2a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml @@ -21,7 +21,6 @@ import ".." Rectangle { id: root property var wizard - property ColorScheme colorScheme: wizard.colorScheme color: colorScheme.background_weak readonly property bool genericClient: SetupWizard.Client.Generic === wizard.client @@ -42,7 +41,7 @@ Rectangle { Label { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme horizontalAlignment: Text.AlignHCenter text: qsTr("Configure %1").arg(wizard.clientName()) type: Label.LabelType.Heading @@ -54,7 +53,7 @@ Rectangle { Layout.fillWidth: true Layout.topMargin: 8 color: colorScheme.text_weak - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme horizontalAlignment: Text.AlignHCenter text: genericClient ? qsTr("Here are the IMAP and SMTP configuration parameters for your email client") : qsTr("Here are your email configuration parameters for %1. \nWe have prepared an easy to follow configuration guide to help you setup your account in %1.").arg(wizard.clientName()) @@ -70,7 +69,7 @@ Rectangle { spacing: 64 Configuration { Layout.fillWidth: true - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme hostname: Backend.hostname password: wizard.user ? wizard.user.password : "" port: Backend.imapPort.toString() @@ -80,7 +79,7 @@ Rectangle { } Configuration { Layout.fillWidth: true - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme hostname: Backend.hostname password: wizard.user ? wizard.user.password : "" port: Backend.smtpPort.toString() @@ -94,7 +93,7 @@ Rectangle { Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: 444 Layout.topMargin: 32 - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme text: qsTr("Open configuration guide") visible: !genericClient } @@ -103,7 +102,7 @@ Rectangle { Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: 444 Layout.topMargin: 32 - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme text: qsTr("Done") onClicked: wizard.closeWizard() } @@ -114,7 +113,7 @@ Rectangle { anchors.bottom: parent.bottom anchors.bottomMargin: 48 anchors.right: parent.right - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme horizontalAlignment: Text.AlignRight text: link("#", qsTr("Report problem")) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml index 9fb87bc8..e4058435 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml @@ -20,7 +20,6 @@ import Proton Item { id: root - property ColorScheme colorScheme: wizard.colorScheme readonly property bool onMacOS: (Backend.goos === "darwin") readonly property bool onWindows: (Backend.goos === "windows") property var wizard @@ -34,7 +33,7 @@ Item { Label { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme text: qsTr("Select your email application") type: Label.LabelType.Heading } @@ -43,7 +42,7 @@ Item { } ClientListItem { Layout.fillWidth: true - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme iconSource: "/qml/icons/ic-apple-mail.svg" text: "Apple Mail" visible: root.onMacOS @@ -54,7 +53,7 @@ Item { } ClientListItem { Layout.fillWidth: true - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme iconSource: "/qml/icons/ic-microsoft-outlook.svg" text: "Microsoft Outlook" visible: root.onMacOS || root.onWindows @@ -66,7 +65,7 @@ Item { } ClientListItem { Layout.fillWidth: true - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme iconSource: "/qml/icons/ic-mozilla-thunderbird.svg" text: "Mozilla Thunderbird" @@ -77,7 +76,7 @@ Item { } ClientListItem { Layout.fillWidth: true - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme iconSource: "/qml/icons/ic-other-mail-clients.svg" text: "Other" @@ -91,7 +90,7 @@ Item { } Button { Layout.fillWidth: true - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme secondary: true text: qsTr("Cancel") diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigWarning.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigWarning.qml index 60bab587..c1cd9f73 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigWarning.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigWarning.qml @@ -39,7 +39,6 @@ import Proton Item { id: root - property ColorScheme colorScheme: wizard.colorScheme property var wizard ColumnLayout { @@ -50,7 +49,7 @@ Item { Label { Layout.fillWidth: true - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme horizontalAlignment: Text.AlignHCenter text: qsTr("A word of warning") type: Label.LabelType.Heading @@ -63,7 +62,7 @@ Item { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme text: qsTr("Do not enter your Proton account password in you email application.") type: Label.LabelType.Body wrapMode: Text.WordWrap @@ -74,7 +73,7 @@ Item { Label { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme horizontalAlignment: Text.AlignHCenter text: qsTr("We have generated a new password for you. It will work only on this computer, and can safely be entered in your email client.") type: Label.LabelType.Body @@ -85,7 +84,7 @@ Item { } Button { Layout.fillWidth: true - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme text: qsTr("I understand") onClicked: { @@ -97,7 +96,7 @@ Item { } Button { Layout.fillWidth: true - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme secondary: true text: qsTr("Cancel") diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 5e9e59d0..8ca9fd19 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -21,7 +21,6 @@ import ".." Item { id: root property var wizard - property ColorScheme colorScheme function showClientSelector() { titleLabel.text = qsTr("Configure your email client"); @@ -71,7 +70,7 @@ Item { } function showLoginMailboxPassword() { - root.description = qsTr("You have secured your account with a separate mailbox password."); + descriptionLabel.text = qsTr("You have secured your account with a separate mailbox password."); linkLabel1.clear(); linkLabel2.clear(); showLoginCommon(); @@ -127,7 +126,7 @@ Item { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true Layout.topMargin: 16 - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme horizontalAlignment: Text.AlignHCenter text: "" type: Label.LabelType.Heading @@ -138,7 +137,7 @@ Item { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true Layout.topMargin: 96 - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme horizontalAlignment: Text.AlignHCenter text: "" type: Label.LabelType.Body @@ -148,14 +147,14 @@ Item { id: linkLabel1 Layout.alignment: Qt.AlignHCenter Layout.topMargin: 96 - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme visible: (text !== "") } LinkLabel { id: linkLabel2 Layout.alignment: Qt.AlignHCenter Layout.topMargin: 16 - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme visible: (text !== "") } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml index ad5a23f7..6036eed2 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml @@ -20,7 +20,7 @@ import Proton FocusScope { id: root - property ColorScheme colorScheme + property var wizard property alias currentIndex: stackLayout.currentIndex property alias username: usernameTextField.text @@ -141,7 +141,7 @@ FocusScope { Label { Layout.alignment: Qt.AlignHCenter - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme text: qsTr("Sign in") type: Label.LabelType.Heading } @@ -149,8 +149,8 @@ FocusScope { id: subTitle Layout.alignment: Qt.AlignHCenter Layout.topMargin: 8 - color: root.colorScheme.text_weak - colorScheme: root.colorScheme + color: wizard.colorScheme.text_weak + colorScheme: wizard.colorScheme text: qsTr("Enter your Proton Account details.") type: Label.LabelType.Lead } @@ -161,7 +161,7 @@ FocusScope { visible: errorLabel.text.length > 0 ColorImage { - color: root.colorScheme.signal_danger + color: wizard.colorScheme.signal_danger height: errorLabel.lineHeight source: "/qml/icons/ic-exclamation-circle-filled.svg" sourceSize.height: errorLabel.lineHeight @@ -170,8 +170,8 @@ FocusScope { id: errorLabel Layout.fillWidth: true Layout.leftMargin: 4 - color: root.colorScheme.signal_danger - colorScheme: root.colorScheme + color: wizard.colorScheme.signal_danger + colorScheme: wizard.colorScheme type: root.error ? Label.LabelType.Caption_semibold : Label.LabelType.Caption wrapMode: Text.WordWrap } @@ -180,7 +180,7 @@ FocusScope { id: usernameTextField Layout.fillWidth: true Layout.topMargin: 48 - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme focus: true label: qsTr("Email or username") validateOnEditingFinished: false @@ -204,7 +204,7 @@ FocusScope { id: passwordTextField Layout.fillWidth: true Layout.topMargin: 48 - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme echoMode: TextInput.Password label: qsTr("Password") validateOnEditingFinished: false @@ -240,7 +240,7 @@ FocusScope { Layout.fillWidth: true Layout.topMargin: 48 - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme enabled: !loading text: loading ? qsTr("Signing in") : qsTr("Sign in") @@ -251,7 +251,7 @@ FocusScope { Button { Layout.fillWidth: true Layout.topMargin: 32 - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme enabled: !signInButton.loading secondary: true text: qsTr("Cancel") @@ -275,7 +275,7 @@ FocusScope { Label { Layout.alignment: Qt.AlignCenter - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme text: qsTr("Two-factor authentication") type: Label.LabelType.Heading } @@ -283,8 +283,8 @@ FocusScope { id: twoFactorUsernameLabel Layout.alignment: Qt.AlignCenter Layout.topMargin: 8 - color: root.colorScheme.text_weak - colorScheme: root.colorScheme + color: wizard.colorScheme.text_weak + colorScheme: wizard.colorScheme type: Label.LabelType.Lead } TextField { @@ -292,7 +292,7 @@ FocusScope { Layout.fillWidth: true Layout.topMargin: 32 assistiveText: qsTr("Enter the 6-digit code") - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme label: qsTr("Two-factor code") validateOnEditingFinished: false validator: function (str) { @@ -314,7 +314,7 @@ FocusScope { id: twoFAButton Layout.fillWidth: true Layout.topMargin: 48 - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme enabled: !loading text: loading ? qsTr("Authenticating") : qsTr("Authenticate") @@ -331,7 +331,7 @@ FocusScope { Button { Layout.fillWidth: true Layout.topMargin: 32 - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme enabled: !twoFAButton.loading secondary: true text: qsTr("Cancel") @@ -355,22 +355,22 @@ FocusScope { Label { Layout.alignment: Qt.AlignCenter - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme text: qsTr("Unlock your mailbox") type: Label.LabelType.Heading } Label { Layout.alignment: Qt.AlignCenter Layout.topMargin: 8 - color: root.colorScheme.text_weak - colorScheme: root.colorScheme + color: wizard.colorScheme.text_weak + colorScheme: wizard.colorScheme type: Label.LabelType.Lead } TextField { id: secondPasswordTextField Layout.fillWidth: true Layout.topMargin: 48 - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme echoMode: TextInput.Password label: qsTr("Mailbox password") validateOnEditingFinished: false @@ -388,7 +388,7 @@ FocusScope { id: secondPasswordButton Layout.fillWidth: true Layout.topMargin: 48 - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme enabled: !loading text: loading ? qsTr("Unlocking") : qsTr("Unlock") @@ -405,7 +405,7 @@ FocusScope { Button { Layout.fillWidth: true Layout.topMargin: 32 - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme enabled: !secondPasswordButton.loading secondary: true text: qsTr("Cancel") diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml index 1e40b491..dc7f6e10 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml @@ -19,10 +19,7 @@ import "." as Proton Item { id: root - - property ColorScheme colorScheme - - signal onboardingAccepted + property var wizard ColumnLayout { anchors.left: parent.left @@ -33,20 +30,20 @@ Item { Label { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme horizontalAlignment: Text.AlignHCenter text: qsTr("Two-step process") type: Label.LabelType.Heading } StepDescriptionBox { - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme description: qsTr("Connect Bridge to your Proton account") icon: "/qml/icons/ic-bridge.svg" iconSize: 48 title: qsTr("Step 1") } StepDescriptionBox { - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme description: qsTr("Connect your email client to Bridge") icon: "/qml/icons/img-mail-clients.svg" iconSize: 64 @@ -55,10 +52,10 @@ Item { Button { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - colorScheme: root.colorScheme + colorScheme: wizard.colorScheme text: qsTr("Let's start") - onClicked: root.onboardingAccepted(); + onClicked: wizard.showLogin(); } } } \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 2e12f73f..50c5ba2c 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -91,13 +91,13 @@ Item { rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigOutlookSelector; } - function start() { + function showOnboarding() { rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; leftContent.showOnboarding(); rightContent.currentIndex = SetupWizard.ContentStack.Onboarding; } - function startClientConfig(user, address) { + function showClientConfig(user, address) { root.user = user root.address = address rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; @@ -105,7 +105,7 @@ Item { rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigSelector; } - function startLogin(username = "") { + function showLogin(username = "") { rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; root.address = ""; leftContent.showLogin(); @@ -133,7 +133,7 @@ Item { } let user = Backend.users.get(userIndex) let address = user ? user.addresses[0] : "" - startClientConfig(user, address); + showClientConfig(user, address); } target: Backend @@ -143,7 +143,7 @@ Item { id: rootStackLayout anchors.fill: parent - // rootStackLayout index 0 SetupWizard.RootStack.TwoPanesView + // rootStackLayout index 0 RowLayout { Layout.fillHeight: true Layout.fillWidth: true @@ -164,7 +164,6 @@ Item { anchors.top: parent.top anchors.topMargin: 96 clip: true - colorScheme: root.colorScheme width: 444 wizard: root } @@ -196,42 +195,40 @@ Item { currentIndex: 0 width: 444 - // stack index 0 + // rightContent stack index 0 Onboarding { Layout.fillHeight: true Layout.fillWidth: true - colorScheme: root.colorScheme - - onOnboardingAccepted: root.startLogin() + wizard: root } - // stack index 1 + // rightContent tack index 1 Login { id: login Layout.fillHeight: true Layout.fillWidth: true - colorScheme: root.colorScheme + wizard: root onLoginAbort: { root.closeWizard(); } } - // stack index 2 + // rightContent stack index 2 ClientConfigSelector { id: clientConfigSelector Layout.fillHeight: true Layout.fillWidth: true wizard: root } - // stack index 3 + // rightContent stack index 3 ClientConfigOutlookSelector { id: clientConfigOutlookSelector Layout.fillHeight: true Layout.fillWidth: true wizard: root } - // stack index 4 + // rightContent stack index 4 ClientConfigWarning { id: clientConfigWarning Layout.fillHeight: true @@ -257,7 +254,7 @@ Item { } } - // rootStackLayout index 1 SetupWizard.RootStack.ClientConfigParameters + // rootStackLayout index 1 ClientConfigParameters { id: clientConfigParameters Layout.fillHeight: true diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml index 652e7894..f76962b7 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml @@ -37,7 +37,6 @@ Item { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.preferredHeight: iconSize Layout.preferredWidth: iconSize - antialiasing: true mipmap: true source: root.icon } From 0a51c7a6b060921e39793d7bbbca6facefae5e94 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Fri, 11 Aug 2023 11:28:54 +0200 Subject: [PATCH 26/93] feat(GODT-2769): Setup Wizard QML foundations. --- .../bridge-gui/bridge-gui/qml/AccountView.qml | 10 +- .../bridge-gui/qml/ContentWrapper.qml | 22 ++-- .../bridge-gui/bridge-gui/qml/MainWindow.qml | 86 ++++++------- .../bridge-gui/qml/Proton/TextArea.qml | 6 +- .../SetupWizard/ClientConfigParameters.qml | 20 +-- .../qml/SetupWizard/ClientConfigWarning.qml | 2 +- .../bridge-gui/qml/SetupWizard/LeftPane.qml | 24 ++-- .../bridge-gui/qml/SetupWizard/Login.qml | 47 ++++--- .../bridge-gui/qml/SetupWizard/Onboarding.qml | 3 +- .../qml/SetupWizard/SetupWizard.qml | 121 ++++++++---------- 10 files changed, 158 insertions(+), 183 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml b/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml index f3a6b70a..cea82cb9 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml @@ -28,8 +28,8 @@ Item { property var notifications property var user - signal showSetupGuide(var user, string address) - signal showSignIn(var username) + signal showClientConfigurator(var user, string address) + signal showLogin(var username) Rectangle { anchors.fill: parent @@ -93,7 +93,7 @@ Item { onClicked: { if (user) { - root.showSignIn(user.primaryEmailOrUsername()); + root.showLogin(user.primaryEmailOrUsername()); } } } @@ -129,7 +129,7 @@ Item { onClicked: { if (!root.user) return; - root.showSetupGuide(root.user, user.addresses[0]); + root.showClientConfigurator(root.user, user.addresses[0]); } } SettingsItem { @@ -171,7 +171,7 @@ Item { onClicked: { if (!root.user) return; - root.showSetupGuide(root.user, addressSelector.displayText); + root.showClientConfigurator(root.user, addressSelector.displayText); } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml b/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml index 6f2ed1f8..5cc343ab 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml @@ -24,9 +24,8 @@ Item { signal closeWindow signal quitBridge - signal showSetupGuide(var user, string address) - signal showSignIn(var username) - signal showSetupWizard() + signal showClientConfigurator(var user, string address) + signal showLogin(var username) function selectUser(userID) { const users = Backend.users; @@ -37,7 +36,7 @@ Item { } accounts.currentIndex = i; if (user.state === EUserState.SignedOut) - showSignIn(user.primaryEmailOrUsername()); + showLogin(user.primaryEmailOrUsername()); return; } console.error("User with ID ", userID, " was not found in the account list"); @@ -55,7 +54,6 @@ Item { rightContent.showGeneralSettings(); } - RowLayout { anchors.fill: parent spacing: 0 @@ -235,7 +233,7 @@ Item { if (user.state !== EUserState.SignedOut) { rightContent.showAccount(); } else { - showSignIn(user.primaryEmailOrUsername()); + showLogin(user.primaryEmailOrUsername()); } } } @@ -283,7 +281,7 @@ Item { width: 36 onClicked: { - root.showSignIn("") + root.showLogin(""); } } } @@ -338,16 +336,16 @@ Item { return Backend.users.get(accounts.currentIndex); } - onShowSetupGuide: function (user, address) { - root.showSetupGuide(user, address); + onShowClientConfigurator: function (user, address) { + root.showClientConfigurator(user, address); } - onShowSignIn: function (username) { - root.showSignIn(username) + onShowLogin: function (username) { + root.showLogin(username); } } Rectangle { - Layout.fillWidth: true Layout.fillHeight: true + Layout.fillWidth: true color: "#ff9900" } GeneralSettings { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index 5aba8680..93f3555a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -26,6 +26,22 @@ ApplicationWindow { property int _defaultWidth: 1080 property var notifications + function layoutForUserCount(userCount) { + if (userCount === 0) { + showLogin(); + return; + } + const u = Backend.users.get(0); + if (!u) { + console.trace(); + console.log("empty user"); + setupWizard.showOnboarding(); + return; + } + if ((userCount === 1) && (u.state === EUserState.SignedOut)) { + setupWizard.showLogin(u.primaryEmailOrUsername()); + } + } function selectUser(userID) { contentWrapper.selectUser(userID); } @@ -36,54 +52,38 @@ ApplicationWindow { root.requestActivate(); } } + function showClientConfigurator(user, address) { + contentLayout.currentIndex = 1; + setupWizard.showClientConfig(user, address); + } function showHelp() { showWebViewOverlay("https://proton.me/support/bridge"); } function showLocalCacheSettings() { contentWrapper.showLocalCacheSettings(); } + function showLogin(username = "") { + contentLayout.currentIndex = 1; + setupWizard.showLogin(username); + } function showSettings() { contentWrapper.showSettings(); } - function showSetup(user, address) { - contentLayout.currentIndex = 1; - setupWizard.showClientConfig(user, address) - } - function showSignIn(username) { - contentLayout.currentIndex = 1; - setupWizard.showLogin(username) - } - function showWebViewOverlay(url) { webViewOverlay.visible = true; webViewOverlay.url = url; } - function layoutForUserCount(userCount) { - if (userCount === 0) { - showSignIn(""); - return; - } - - const u = Backend.users.get(0); - if (!u) { - console.trace(); - console.log("empty user"); - setupWizard.showOnboarding(); - return; - } - - if ((userCount === 1) && (u.state === EUserState.SignedOut)) { - setupWizard.showLogin(u.primaryEmailOrUsername()); - } - } - colorScheme: ProtonStyle.currentStyle height: _defaultHeight minimumWidth: _defaultWidth visible: true width: _defaultWidth + Component.onCompleted: { + layoutForUserCount(Backend.users.count); + } + // show Setup Guide on every new user Connections { function onRowsAboutToBeRemoved(parent, first, last) { @@ -103,7 +103,7 @@ ApplicationWindow { if (user.setupGuideSeen) { return; } - root.showSetup(user, user.addresses[0]); + root.showClientConfigurator(user, user.addresses[0]); } target: Backend.users @@ -129,17 +129,15 @@ ApplicationWindow { target: Backend } - Connections { function onCountChanged(count) { - layoutForUserCount(count) + layoutForUserCount(count); } target: Backend.users } StackLayout { id: contentLayout - anchors.fill: parent currentIndex: 0 @@ -160,29 +158,27 @@ ApplicationWindow { root.close(); Backend.quit(); } - onShowSetupGuide: function (user, address) { - root.showSetup(user, address); + onShowClientConfigurator: function (user, address) { + root.showClientConfigurator(user, address); } - onShowSignIn: function(username) { - root.showSignIn(username) + onShowLogin: function (username) { + root.showLogin(username); } } - SetupWizard { id: setupWizard - Layout.fillWidth: true; - Layout.fillHeight: true; + Layout.fillHeight: true + Layout.fillWidth: true colorScheme: root.colorScheme - onWizardEnded: { - contentLayout.currentIndex = 0 - } onShowBugReport: { contentWrapper.showBugReport(); } + onWizardEnded: { + contentLayout.currentIndex = 0; + } } } - WebView { id: webViewOverlay anchors.fill: parent @@ -200,8 +196,4 @@ ApplicationWindow { id: splashScreen colorScheme: root.colorScheme } - - Component.onCompleted: { - layoutForUserCount(Backend.users.count) - } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/TextArea.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/TextArea.qml index 28679000..d0551c57 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/TextArea.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/TextArea.qml @@ -238,12 +238,12 @@ FocusScope { bottomPadding: 8 color: { if (!control.enabled) { - return root.colorScheme.text_disabled + return root.colorScheme.text_disabled; } if (control.readOnly) { - return root.colorScheme.text_hint + return root.colorScheme.text_hint; } - return root.colorScheme.text_norm + return root.colorScheme.text_norm; } // enforcing default focus here within component diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml index 224e8b2a..3dbc38d2 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml @@ -20,24 +20,27 @@ import ".." Rectangle { id: root - property var wizard - color: colorScheme.background_weak + readonly property bool genericClient: SetupWizard.Client.Generic === wizard.client + property var wizard + + color: colorScheme.background_weak Item { id: centeredContainer + anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top - anchors.bottom: parent.bottom width: 800 ColumnLayout { + anchors.bottomMargin: 96 anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top - anchors.bottomMargin: 96 anchors.topMargin: 32 spacing: 0 + Label { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true @@ -55,18 +58,17 @@ Rectangle { color: colorScheme.text_weak colorScheme: wizard.colorScheme horizontalAlignment: Text.AlignHCenter - text: genericClient ? qsTr("Here are the IMAP and SMTP configuration parameters for your email client") : - qsTr("Here are your email configuration parameters for %1. \nWe have prepared an easy to follow configuration guide to help you setup your account in %1.").arg(wizard.clientName()) + text: genericClient ? qsTr("Here are the IMAP and SMTP configuration parameters for your email client") : qsTr("Here are your email configuration parameters for %1. \nWe have prepared an easy to follow configuration guide to help you setup your account in %1.").arg(wizard.clientName()) type: Label.LabelType.Body wrapMode: Text.WordWrap } RowLayout { id: configuration - Layout.fillHeight: true Layout.fillWidth: true Layout.topMargin: 32 spacing: 64 + Configuration { Layout.fillWidth: true colorScheme: wizard.colorScheme @@ -88,7 +90,6 @@ Rectangle { username: wizard.address } } - Button { Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: 444 @@ -97,17 +98,16 @@ Rectangle { text: qsTr("Open configuration guide") visible: !genericClient } - Button { Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: 444 Layout.topMargin: 32 colorScheme: wizard.colorScheme text: qsTr("Done") + onClicked: wizard.closeWizard() } } - LinkLabel { id: reportProblemLink anchors.bottom: parent.bottom diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigWarning.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigWarning.qml index c1cd9f73..b4db56ba 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigWarning.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigWarning.qml @@ -61,8 +61,8 @@ Item { Label { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter text: qsTr("Do not enter your Proton account password in you email application.") type: Label.LabelType.Body wrapMode: Text.WordWrap diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 8ca9fd19..8edd5842 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -20,15 +20,8 @@ import ".." Item { id: root - property var wizard - function showClientSelector() { - titleLabel.text = qsTr("Configure your email client"); - descriptionLabel.text = qsTr("Bridge is now connected to Proton, and has already started downloading your messages. Let’s now connect your email client to Bridge."); - linkLabel1.clear(); - linkLabel2.clear(); - icon.source = "/qml/icons/img-mail-clients.svg"; - } + property var wizard function showClientConfigCommon() { const clientName = wizard.clientName(); @@ -40,26 +33,29 @@ Item { Layout.preferredHeight = 72; Layout.preferredWidth = 72; } - function showClientConfigWarning() { showClientConfigCommon(); linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why can't I use my Proton password in my email client?")); } - + function showClientSelector() { + titleLabel.text = qsTr("Configure your email client"); + descriptionLabel.text = qsTr("Bridge is now connected to Proton, and has already started downloading your messages. Let’s now connect your email client to Bridge."); + linkLabel1.clear(); + linkLabel2.clear(); + icon.source = "/qml/icons/img-mail-clients.svg"; + } function showLogin() { descriptionLabel.text = qsTr("Let's start by signing in to your Proton account."); linkLabel1.setLink("https://proton.me/mail/pricing", qsTr("Create or upgrade your account")); linkLabel2.clear(); showLoginCommon(); } - function showLogin2FA() { descriptionLabel.text = qsTr("You have enabled two-factor authentication. Please enter the 6-digit code provided by your authenticator application."); linkLabel1.clear(); linkLabel2.clear(); showLoginCommon(); } - function showLoginCommon() { titleLabel.text = qsTr("Sign in to your Proton Account"); icon.Layout.preferredHeight = 72; @@ -68,14 +64,12 @@ Item { icon.sourceSize.height = 128; icon.sourceSize.width = 128; } - function showLoginMailboxPassword() { descriptionLabel.text = qsTr("You have secured your account with a separate mailbox password."); linkLabel1.clear(); linkLabel2.clear(); showLoginCommon(); } - function showOnboarding() { titleLabel.text = qsTr("Welcome to\nProton Mail Bridge"); descriptionLabel.text = qsTr("Bridge is the gateway between your Proton account and your email client. It runs in the background and encrypts and decrypts your messages seamlessly. "); @@ -87,7 +81,6 @@ Item { icon.sourceSize.height = 148; icon.sourceSize.width = 265; } - function showOutlookSelector() { showClientConfigCommon(); linkLabel1.setLink("https://proton.me/support/bridge", qsTr("My version of Outlook is not listed")); @@ -98,7 +91,6 @@ Item { function onLogin2FARequested() { showLogin2FA(); } - function onLogin2PasswordRequested() { showLoginMailboxPassword(); } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml index 6036eed2..4f557b62 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml @@ -19,10 +19,15 @@ import Proton FocusScope { id: root + enum RootStack { + Login, + TOTP, + MailboxPassword + } - property var wizard property alias currentIndex: stackLayout.currentIndex property alias username: usernameTextField.text + property var wizard signal loginAbort(string username, bool wasSignedOut) @@ -32,10 +37,10 @@ FocusScope { Backend.loginAbort(usernameTextField.text); } function reset(clearUsername = false) { - stackLayout.currentIndex = 0; - loginNormalLayout.reset(clearUsername); - login2FALayout.reset(); - login2PasswordLayout.reset(); + stackLayout.currentIndex = Login.RootStack.Login; + loginLayout.reset(clearUsername); + totpLayout.reset(); + mailboxPasswordLayout.reset(); } implicitHeight: children[0].implicitHeight @@ -55,7 +60,7 @@ FocusScope { Connections { function onLogin2FAError(_) { - console.assert(stackLayout.currentIndex === 1, "Unexpected login2FAError"); + console.assert(stackLayout.currentIndex === Login.RootStack.TOTP, "Unexpected login2FAError"); twoFAButton.loading = false; twoFactorPasswordTextField.enabled = true; twoFactorPasswordTextField.error = true; @@ -63,18 +68,18 @@ FocusScope { twoFactorPasswordTextField.focus = true; } function onLogin2FAErrorAbort(_) { - console.assert(stackLayout.currentIndex === 1, "Unexpected login2FAErrorAbort"); + console.assert(stackLayout.currentIndex === Login.RootStack.TOTP, "Unexpected login2FAErrorAbort"); root.reset(); errorLabel.text = qsTr("Incorrect login credentials. Please try again."); } function onLogin2FARequested(username) { - console.assert(stackLayout.currentIndex === 0, "Unexpected login2FARequested"); + console.assert(stackLayout.currentIndex === Login.RootStack.Login, "Unexpected login2FARequested"); twoFactorUsernameLabel.text = username; - stackLayout.currentIndex = 1; + stackLayout.currentIndex = Login.RootStack.TOTP; twoFactorPasswordTextField.focus = true; } function onLogin2PasswordError(_) { - console.assert(stackLayout.currentIndex === 2, "Unexpected login2PasswordError"); + console.assert(stackLayout.currentIndex === Login.RootStack.MailboxPassword, "Unexpected login2PasswordError"); secondPasswordButton.loading = false; secondPasswordTextField.enabled = true; secondPasswordTextField.error = true; @@ -82,34 +87,34 @@ FocusScope { secondPasswordTextField.focus = true; } function onLogin2PasswordErrorAbort(_) { - console.assert(stackLayout.currentIndex === 2, "Unexpected login2PasswordErrorAbort"); + console.assert(stackLayout.currentIndex === Login.RootStack.MailboxPassword, "Unexpected login2PasswordErrorAbort"); root.reset(); errorLabel.text = qsTr("Incorrect login credentials. Please try again."); } function onLogin2PasswordRequested() { - console.assert(stackLayout.currentIndex === 0 || stackLayout.currentIndex === 1, "Unexpected login2PasswordRequested"); - stackLayout.currentIndex = 2; + console.assert(stackLayout.currentIndex === Login.RootStack.Login || stackLayout.currentIndex === Login.RootStack.TOTP, "Unexpected login2PasswordRequested"); + stackLayout.currentIndex = Login.RootStack.MailboxPassword; secondPasswordTextField.focus = true; } function onLoginAlreadyLoggedIn(_) { - stackLayout.currentIndex = 0; + stackLayout.currentIndex = Login.RootStack.Login; root.reset(); } function onLoginConnectionError(_) { - if (stackLayout.currentIndex === 0) { + if (stackLayout.currentIndex === Login.RootStack.Login) { stackLayout.loginFailed(); } } function onLoginFinished(_) { - stackLayout.currentIndex = 0; + stackLayout.currentIndex = Login.RootStack.Login; root.reset(); } function onLoginFreeUserError() { - console.assert(stackLayout.currentIndex === 0, "Unexpected loginFreeUserError"); + console.assert(stackLayout.currentIndex === Login.RootStack.Login, "Unexpected loginFreeUserError"); stackLayout.loginFailed(); } function onLoginUsernamePasswordError(errorMsg) { - console.assert(stackLayout.currentIndex === 0, "Unexpected loginUsernamePasswordError"); + console.assert(stackLayout.currentIndex === Login.RootStack.Login, "Unexpected loginUsernamePasswordError"); stackLayout.loginFailed(); if (errorMsg !== "") errorLabel.text = errorMsg; @@ -120,7 +125,7 @@ FocusScope { target: Backend } ColumnLayout { - id: loginNormalLayout + id: loginLayout function reset(clearUsername = false) { signInButton.loading = false; errorLabel.text = ""; @@ -262,7 +267,7 @@ FocusScope { } } ColumnLayout { - id: login2FALayout + id: totpLayout function reset() { twoFAButton.loading = false; twoFactorPasswordTextField.enabled = true; @@ -342,7 +347,7 @@ FocusScope { } } ColumnLayout { - id: login2PasswordLayout + id: mailboxPasswordLayout function reset() { secondPasswordButton.loading = false; secondPasswordTextField.enabled = true; diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml index dc7f6e10..5338cceb 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml @@ -19,6 +19,7 @@ import "." as Proton Item { id: root + property var wizard ColumnLayout { @@ -55,7 +56,7 @@ Item { colorScheme: wizard.colorScheme text: qsTr("Let's start") - onClicked: wizard.showLogin(); + onClicked: wizard.showLogin() } } } \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 50c5ba2c..a85181b5 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -26,85 +26,75 @@ Item { MozillaThunderbird, Generic } - - enum RootStack { - TwoPanesView = 0, - ClientConfigParameters = 1 - } - enum ContentStack { - Onboarding = 0, - Login = 1, - ClientConfigSelector = 2, - ClientConfigOutlookSelector = 3, - ClientConfigWarning = 4 + Onboarding, + Login, + ClientConfigSelector, + ClientConfigOutlookSelector, + ClientConfigWarning + } + enum RootStack { + TwoPanesView, + ClientConfigParameters } + property string address property int client property string clientVersion property ColorScheme colorScheme property var user - property string address - signal wizardEnded() - signal showBugReport() + signal showBugReport + signal wizardEnded function clientIconSource() { switch (client) { - case SetupWizard.Client.AppleMail: - return "/qml/icons/ic-apple-mail.svg"; - case SetupWizard.Client.MicrosoftOutlook: - return "/qml/icons/ic-microsoft-outlook.svg"; - case SetupWizard.Client.MozillaThunderbird: - return "/qml/icons/ic-mozilla-thunderbird.svg"; - case SetupWizard.Client.Generic: - return "/qml/icons/ic-other-mail-clients.svg"; - default: - console.error("Unknown mail client " + client) - return "/qml/icons/ic-other-mail-clients.svg"; + case SetupWizard.Client.AppleMail: + return "/qml/icons/ic-apple-mail.svg"; + case SetupWizard.Client.MicrosoftOutlook: + return "/qml/icons/ic-microsoft-outlook.svg"; + case SetupWizard.Client.MozillaThunderbird: + return "/qml/icons/ic-mozilla-thunderbird.svg"; + case SetupWizard.Client.Generic: + return "/qml/icons/ic-other-mail-clients.svg"; + default: + console.error("Unknown mail client " + client); + return "/qml/icons/ic-other-mail-clients.svg"; } } - function clientName() { switch (client) { - case SetupWizard.Client.AppleMail: - return "Apple Mail"; - case SetupWizard.Client.MicrosoftOutlook: - return "Outlook"; - case SetupWizard.Client.MozillaThunderbird: - return "Thunderbird"; - case SetupWizard.Client.Generic: - return "your email client"; - default: - console.error("Unknown mail client " + client) - return "your email client"; + case SetupWizard.Client.AppleMail: + return "Apple Mail"; + case SetupWizard.Client.MicrosoftOutlook: + return "Outlook"; + case SetupWizard.Client.MozillaThunderbird: + return "Thunderbird"; + case SetupWizard.Client.Generic: + return "your email client"; + default: + console.error("Unknown mail client " + client); + return "your email client"; } } - function closeWizard() { - wizardEnded() + wizardEnded(); } - - function showOutlookSelector() { - rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; - leftContent.showOutlookSelector(); - rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigOutlookSelector; - } - - function showOnboarding() { - rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; - leftContent.showOnboarding(); - rightContent.currentIndex = SetupWizard.ContentStack.Onboarding; - } - function showClientConfig(user, address) { - root.user = user - root.address = address + root.user = user; + root.address = address; rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; leftContent.showClientSelector(); rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigSelector; } - + function showClientParams() { + rootStackLayout.currentIndex = SetupWizard.RootStack.ClientConfigParameters; + } + function showClientWarning() { + rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; + leftContent.showClientConfigWarning(); + rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigWarning; + } function showLogin(username = "") { rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; root.address = ""; @@ -113,16 +103,15 @@ Item { login.reset(true); login.username = username; } - - function showClientWarning() { + function showOnboarding() { rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; - leftContent.showClientConfigWarning(); - rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigWarning + leftContent.showOnboarding(); + rightContent.currentIndex = SetupWizard.ContentStack.Onboarding; } - - function showClientParams() { - rootStackLayout.currentIndex = SetupWizard.RootStack.ClientConfigParameters; - + function showOutlookSelector() { + rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; + leftContent.showOutlookSelector(); + rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigOutlookSelector; } Connections { @@ -131,14 +120,13 @@ Item { closeWizard(); return; } - let user = Backend.users.get(userIndex) - let address = user ? user.addresses[0] : "" + let user = Backend.users.get(userIndex); + let address = user ? user.addresses[0] : ""; showClientConfig(user, address); } target: Backend } - StackLayout { id: rootStackLayout anchors.fill: parent @@ -157,7 +145,6 @@ Item { LeftPane { id: leftContent - anchors.bottom: parent.bottom anchors.bottomMargin: 96 anchors.horizontalCenter: parent.horizontalCenter From f48a60d58cc7ddf89401c46cdceccfed1f8fa368 Mon Sep 17 00:00:00 2001 From: Jakub Date: Mon, 7 Aug 2023 07:12:37 +0000 Subject: [PATCH 27/93] feat(GODT-2762): bump version Go 1.20 Qt 6.4.3. --- .gitlab-ci.yml | 6 +++--- BUILDS.md | 4 ++-- utils/credits.sh | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1eb2fe88..2dacf749 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -71,7 +71,7 @@ stages: - export GO111MODULE=on - export PATH="${GOPATH}/bin:${PATH}" - export MSYSTEM= - - export QT6DIR=/c/grrrQt/6.3.2/msvc2019_64 + - export QT6DIR=/c/grrrQt/6.4.3/msvc2019_64 - export PATH=$PATH:${QT6DIR}/bin - export PATH="/c/Program Files/Microsoft Visual Studio/2022/Community/Common7/IDE/CommonExtensions/Microsoft/CMake/CMake/bin:$PATH" - $(git config --global -l | grep -o 'url.*gitlab.protontech.ch.*insteadof' | xargs -L 1 git config --global --unset &> /dev/null) || echo "nothing to remove" @@ -93,7 +93,7 @@ stages: - export PATH="${GOROOT}/bin:$PATH" - export GOPATH=~/go1.20 - export PATH="${GOPATH}/bin:$PATH" - - export QT6DIR=/opt/Qt/6.3.2/macos + - export QT6DIR=/opt/Qt/6.4.3/macos - export PATH="${QT6DIR}/bin:$PATH" - uname -a cache: {} @@ -101,7 +101,7 @@ stages: - macos-m1-bridge .env-linux-build: - image: gitlab.protontech.ch:4567/go/bridge-internal:build-go1.20-qt6.3.2 + image: gitlab.protontech.ch:4567/go/bridge-internal:build-go1.20-qt6.4.3 variables: VCPKG_DEFAULT_BINARY_CACHE: ${CI_PROJECT_DIR}/.cache cache: diff --git a/BUILDS.md b/BUILDS.md index 237694bd..261ecd69 100644 --- a/BUILDS.md +++ b/BUILDS.md @@ -10,7 +10,7 @@ * Windres (Windows) * libglvnd and libsecret development files (Linux) * pkg-config (Linux) -* cmake, ninja-build and Qt 6 are required to build the graphical user interface. On Linux, +* cmake, ninja-build and Qt 6.4.3 are required to build the graphical user interface. On Linux, the Mesa OpenGL development files are also needed. To enable the sending of crash reports using Sentry please set the @@ -19,7 +19,7 @@ Otherwise, the sending of crash reports will be disabled. ## Build In order to build Bridge app with Qt interface we are using -[Qt 6.3](https://doc.qt.io/qt-6/gettingstarted.html). +[Qt 6.4.3](https://doc.qt.io/qt-6/gettingstarted.html). Please note that qmake path must be in your `PATH` to ensure Qt to be found. Also, before you start build **on Windows**, please unset the `MSYSTEM` variable diff --git a/utils/credits.sh b/utils/credits.sh index 9ffe77d7..a0b4c24f 100755 --- a/utils/credits.sh +++ b/utils/credits.sh @@ -30,7 +30,7 @@ egrep $'^\t[^=>]*$' $LOCKFILE | sed -r 's/\t([^ ]*) v.*/\1/g' > $TEMPFILE1 egrep $'^\t.*=>.*v.*$' $LOCKFILE | sed -r 's/^.*=> ([^ ]*)( v.*)?/\1/g' >> $TEMPFILE1 cat $TEMPFILE1 | egrep -v 'therecipe/qt/internal|therecipe/env_.*_512|protontech' | sort | uniq > $TEMPFILE2 # Add non vendor credits -echo -e "\nQt 6.3.1 by Qt group\n" >> $TEMPFILE2 +echo -e "\nQt 6.4.3 by Qt group\n" >> $TEMPFILE2 # join lines sed -i -e ':a' -e 'N' -e '$!ba' -e 's|\n|;|g' $TEMPFILE2 From 9b546b5412e1d5889f8facb96a917f1ca9c873fa Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 8 Aug 2023 08:31:11 +0200 Subject: [PATCH 28/93] feat(GODT-2762): adjust mac and windows qt deploy * do not remove web engine frameworks from macos bundle * add libs, QML files, resources, translations needed for WebView * ship QWebEngineProcess in linux and windows builds --- Makefile | 3 --- .../bridge-gui/bridge-gui/DeployDarwin.cmake | 4 ++++ .../bridge-gui/bridge-gui/DeployLinux.cmake | 16 +++++++++++++++- .../bridge-gui/bridge-gui/DeployWindows.cmake | 2 ++ 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 6f010be6..b4653fc7 100644 --- a/Makefile +++ b/Makefile @@ -139,9 +139,6 @@ ${DEPLOY_DIR}/darwin: ${EXE_TARGET} build-launcher perl -i -pe"s/>${BRIDGE_GUI_EXE_NAME}/>${LAUNCHER_EXE}/g" ${DARWINAPP_CONTENTS}/Info.plist cp ./dist/${SRC_ICNS} ${DARWINAPP_CONTENTS}/Resources/${SRC_ICNS} cp LICENSE ${DARWINAPP_CONTENTS}/Resources/ - rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebEngine.framework" - rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebView.framework" - rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebEngineCore.framework" mv ${LAUNCHER_EXE} ${DARWINAPP_CONTENTS}/MacOS/${LAUNCHER_EXE} ./utils/remove_non_relative_links_darwin.sh "${EXE_TARGET_DARWIN}/${EXE_BINARY_DARWIN}" diff --git a/internal/frontend/bridge-gui/bridge-gui/DeployDarwin.cmake b/internal/frontend/bridge-gui/bridge-gui/DeployDarwin.cmake index ce32255b..03fc4b97 100644 --- a/internal/frontend/bridge-gui/bridge-gui/DeployDarwin.cmake +++ b/internal/frontend/bridge-gui/bridge-gui/DeployDarwin.cmake @@ -30,6 +30,8 @@ install(DIRECTORY "${QT_DIR}/qml/QtQml" DESTINATION "${CMAKE_INSTALL_PREFIX}/bridge-gui.app/Contents/MacOS") install(DIRECTORY "${QT_DIR}/qml/QtQuick" DESTINATION "${CMAKE_INSTALL_PREFIX}/bridge-gui.app/Contents/MacOS") +install(DIRECTORY "${QT_DIR}/qml/QtWebView" + DESTINATION "${CMAKE_INSTALL_PREFIX}/bridge-gui.app/Contents/MacOS") # FRAMEWORKS install(DIRECTORY "${QT_DIR}/lib/QtQmlWorkerScript.framework" DESTINATION "${CMAKE_INSTALL_PREFIX}/bridge-gui.app/Contents/Frameworks") @@ -43,6 +45,8 @@ install(DIRECTORY "${QT_DIR}/lib/QtQuickDialogs2QuickImpl.framework" DESTINATION "${CMAKE_INSTALL_PREFIX}/bridge-gui.app/Contents/Frameworks") install(DIRECTORY "${QT_DIR}/lib/QtQuickDialogs2Utils.framework" DESTINATION "${CMAKE_INSTALL_PREFIX}/bridge-gui.app/Contents/Frameworks") +install(DIRECTORY "${QT_DIR}/lib/QtWebViewQuick.framework" + DESTINATION "${CMAKE_INSTALL_PREFIX}/bridge-gui.app/Contents/Frameworks") # PLUGINS install(FILES "${QT_DIR}/plugins/imageformats/libqsvg.dylib" DESTINATION "${CMAKE_INSTALL_PREFIX}/bridge-gui.app/Contents/PlugIns/imageformats") diff --git a/internal/frontend/bridge-gui/bridge-gui/DeployLinux.cmake b/internal/frontend/bridge-gui/bridge-gui/DeployLinux.cmake index 10e1ea16..a97ccb87 100644 --- a/internal/frontend/bridge-gui/bridge-gui/DeployLinux.cmake +++ b/internal/frontend/bridge-gui/bridge-gui/DeployLinux.cmake @@ -22,7 +22,11 @@ cmake_minimum_required(VERSION 3.22) #***************************************************************************************************************************************************** set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_BINDIR}" "${CMAKE_INSTALL_LIBDIR}" "." "../lib") -install(DIRECTORY "${QT_DIR}/qml" "${QT_DIR}/plugins" +install(DIRECTORY + "${QT_DIR}/qml" + "${QT_DIR}/plugins" + "${QT_DIR}/translations" + "${QT_DIR}/resources" DESTINATION "${CMAKE_INSTALL_PREFIX}") macro( AppendLib LIB_NAME HINT_PATH) @@ -68,6 +72,13 @@ AppendQt6Lib("libQt6PrintSupport.so.6") AppendQt6Lib("libQt6Xml.so.6") AppendQt6Lib("libQt6OpenGLWidgets.so.6") AppendQt6Lib("libQt6QuickWidgets.so.6") +AppendQt6Lib("libQt6Positioning.so.6") +AppendQt6Lib("libQt6WebChannel.so.6") +AppendQt6Lib("libQt6WebView.so.6") +AppendQt6Lib("libQt6WebViewQuick.so.6") +AppendQt6Lib("libQt6WebEngineCore.so.6") +AppendQt6Lib("libQt6WebEngineQuick.so.6") +AppendQt6Lib("libQt6WebEngineQuickDelegatesQml.so.6") # QML dependencies AppendQt6Lib("libQt6QmlWorkerScript.so.6") @@ -81,3 +92,6 @@ AppendQt6Lib("libQt6Svg.so.6") AppendQt6Lib("libQt6QmlCore.so.6") install(FILES ${DEPLOY_LIBS} DESTINATION "${CMAKE_INSTALL_PREFIX}/lib") + +# Install QtWebEngineProcess to be able to use WebView +install(PROGRAMS "${QT_DIR}/libexec/QtWebEngineProcess" DESTINATION "${CMAKE_INSTALL_PREFIX}/libexec") diff --git a/internal/frontend/bridge-gui/bridge-gui/DeployWindows.cmake b/internal/frontend/bridge-gui/bridge-gui/DeployWindows.cmake index 85573307..99b0037c 100644 --- a/internal/frontend/bridge-gui/bridge-gui/DeployWindows.cmake +++ b/internal/frontend/bridge-gui/bridge-gui/DeployWindows.cmake @@ -71,6 +71,8 @@ install(FILES ${DEPLOY_LIBS} DESTINATION "${CMAKE_INSTALL_PREFIX}") install(DIRECTORY ${QT_DIR}/qml/Qt/labs/platform DESTINATION "${CMAKE_INSTALL_PREFIX}/Qt/labs/") install(DIRECTORY ${QT_DIR}/qml/QtQml DESTINATION "${CMAKE_INSTALL_PREFIX}") install(DIRECTORY ${QT_DIR}/qml/QtQuick DESTINATION "${CMAKE_INSTALL_PREFIX}") +install(DIRECTORY ${QT_DIR}/qml/QtWebView DESTINATION "${CMAKE_INSTALL_PREFIX}") +install(DIRECTORY ${QT_DIR}/qml/QtWebEngine DESTINATION "${CMAKE_INSTALL_PREFIX}") # crash handler utils install(PROGRAMS "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/tools/sentry-native/crashpad_handler.exe" DESTINATION "${CMAKE_INSTALL_PREFIX}") From bccf31501d2c37e452518c93f2fe9db6a1308e76 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Wed, 16 Aug 2023 16:37:41 +0200 Subject: [PATCH 29/93] feat(GODT-2769): moved LinkLabel QML component to Proton custom component folder. --- internal/frontend/bridge-gui/bridge-gui/Resources.qrc | 2 +- .../bridge-gui/bridge-gui/qml/{ => Proton}/LinkLabel.qml | 4 ---- internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir | 1 + 3 files changed, 2 insertions(+), 5 deletions(-) rename internal/frontend/bridge-gui/bridge-gui/qml/{ => Proton}/LinkLabel.qml (93%) diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index e2c6df5a..a1460606 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -74,7 +74,6 @@ ../../../../dist/bridgeMacOS.svg qml/KeychainSettings.qml qml/LocalCacheSettings.qml - qml/LinkLabel.qml qml/MainWindow.qml qml/NotificationDialog.qml qml/NotificationPopups.qml @@ -91,6 +90,7 @@ qml/Proton/ComboBox.qml qml/Proton/Dialog.qml qml/Proton/Label.qml + qml/Proton/LinkLabel.qml qml/Proton/Menu.qml qml/Proton/MenuItem.qml qml/Proton/Popup.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/LinkLabel.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/LinkLabel.qml similarity index 93% rename from internal/frontend/bridge-gui/bridge-gui/qml/LinkLabel.qml rename to internal/frontend/bridge-gui/bridge-gui/qml/Proton/LinkLabel.qml index 0dae6e7f..4b9274e6 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/LinkLabel.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/LinkLabel.qml @@ -10,12 +10,8 @@ // 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 . -import QtQml import QtQuick -import QtQuick.Layouts import QtQuick.Controls -import QtQuick.Controls.impl -import "." as Proton Label { id: root diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir index 4e6ccf3a..c866fc30 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir @@ -28,6 +28,7 @@ CheckBox 4.0 CheckBox.qml ComboBox 4.0 ComboBox.qml Dialog 4.0 Dialog.qml Label 4.0 Label.qml +LinkLabel 4.0 LinkLabel.qml Menu 4.0 Menu.qml MenuItem 4.0 MenuItem.qml Popup 4.0 Popup.qml From 2d6f42e0b547e78816a3a949d133854359d8173e Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Tue, 15 Aug 2023 16:41:03 +0200 Subject: [PATCH 30/93] feat(GODT-2771): improved macOS cert installation tools. --- internal/certs/cert_store_darwin.go | 405 +++++++++++++++++++---- internal/certs/cert_store_darwin_test.go | 73 +++- internal/certs/installer.go | 4 + 3 files changed, 405 insertions(+), 77 deletions(-) diff --git a/internal/certs/cert_store_darwin.go b/internal/certs/cert_store_darwin.go index 97a79fa5..faafb448 100644 --- a/internal/certs/cert_store_darwin.go +++ b/internal/certs/cert_store_darwin.go @@ -23,71 +23,200 @@ package certs #import #import +// Memory management rules: +// Foundation object (Objective-C prefixed with `NS`) get ARC (Automatic Reference Counting), and do not need to be released manually. +// Core Foundation objects (C), prefixed with need to be released manually using CFRelease() unless: +// - They're obtained using a CF method containing the word Get (a.k.a. the Get Rule). +// - They're obtained using toll-free bridging from a Foundation Object (using the __bridge keyword). -int installTrustedCert(char const *bytes, unsigned long long length) { - if (length == 0) { - return errSecInvalidData; - } - - NSData *der = [NSData dataWithBytes:bytes length:length]; - - // Step 1. Import the certificate in the keychain. - SecCertificateRef cert = SecCertificateCreateWithData(NULL, (CFDataRef) der); - NSDictionary* addQuery = @{ - (id)kSecValueRef: (__bridge id) cert, - (id)kSecClass: (id)kSecClassCertificate, - }; - - OSStatus status = SecItemAdd((__bridge CFDictionaryRef) addQuery, NULL); - if ((errSecSuccess != status) && (errSecDuplicateItem != status)) { - CFRelease(cert); - return status; - } - - // Step 2. Set the trust for the certificate. - SecPolicyRef policy = SecPolicyCreateSSL(true, NULL); // we limit our trust to SSL - NSDictionary *trustSettings = @{ - (id)kSecTrustSettingsResult: [NSNumber numberWithInt:kSecTrustSettingsResultTrustRoot], - (id)kSecTrustSettingsPolicy: (__bridge id) policy, - }; - status = SecTrustSettingsSetTrustSettings(cert, kSecTrustSettingsDomainUser, (__bridge CFTypeRef)(trustSettings)); - CFRelease(policy); - CFRelease(cert); - - return status; +//**************************************************************************************************************************************************** +/// \brief Create a certificate object from DER-encoded data. +/// +/// \return The certifcation. The caller is responsible for releasing the object using CFRelease. +/// \return NULL if data is not a valid DER-encoded certificate. +//**************************************************************************************************************************************************** +SecCertificateRef certFromData(char const* data, uint64_t length) { + NSData *der = [NSData dataWithBytes:data length:length]; + return SecCertificateCreateWithData(NULL, (__bridge CFDataRef)der); } -int removeTrustedCert(char const *bytes, unsigned long long length) { - if (0 == length) { - return errSecInvalidData; - } +//**************************************************************************************************************************************************** +/// \brief Check if a certificate is in the user's keychain. +/// +/// \param[in] cert The certificate. +/// \return true iff the certificate is in the user's keychain. +//**************************************************************************************************************************************************** +bool _isCertificateInKeychain(SecCertificateRef const cert) { + NSDictionary *attrs = @{ + (id)kSecMatchItemList: @[(__bridge id)cert], + (id)kSecClass: (id)kSecClassCertificate, + (id)kSecReturnData: @YES + }; + return errSecSuccess == SecItemCopyMatching((__bridge CFDictionaryRef)attrs, NULL); +} - NSData *der = [NSData dataWithBytes: bytes length: length]; - SecCertificateRef cert = SecCertificateCreateWithData(NULL, (CFDataRef) der); +//**************************************************************************************************************************************************** +/// \brief Check if a certificate is in the user's keychain. +/// +/// \param[in] certData The certificate data in DER encoded format. +/// \param[in] certSize The size of the certData in bytes. +/// \return true iff the certificate is in the user's keychain. +//**************************************************************************************************************************************************** +bool isCertificateInKeychain(char const* certData, uint64_t certSize) { + return _isCertificateInKeychain(certFromData(certData, certSize)); +} - // Step 1. Unset the trust for the certificate. - SecPolicyRef policy = SecPolicyCreateSSL(true, NULL); - NSDictionary * trustSettings = @{ - (id)kSecTrustSettingsResult: [NSNumber numberWithInt:kSecTrustSettingsResultUnspecified], - (id)kSecTrustSettingsPolicy: (__bridge id) policy, - }; - OSStatus status = SecTrustSettingsSetTrustSettings(cert, kSecTrustSettingsDomainUser, (__bridge CFTypeRef)(trustSettings)); - CFRelease(policy); - if (errSecSuccess != status) { - CFRelease(cert); - return status; - } - // Step 2. Remove the certificate from the keychain. - NSDictionary *query = @{ (id)kSecClass: (id)kSecClassCertificate, - (id)kSecMatchItemList: @[(__bridge id)cert], - (id)kSecMatchLimit: (id)kSecMatchLimitOne, - }; - status = SecItemDelete((__bridge CFDictionaryRef) query); +//**************************************************************************************************************************************************** +/// \brief Add a certificate to the user's keychain. +/// +/// \param[in] cert The certificate. +/// \return The status for the operation. +//**************************************************************************************************************************************************** +OSStatus _addCertificateToKeychain(SecCertificateRef const cert) { + NSDictionary* addQuery = @{ + (id)kSecValueRef: (__bridge id) cert, + (id)kSecClass: (id)kSecClassCertificate, + }; + return SecItemAdd((__bridge CFDictionaryRef) addQuery, NULL); +} - CFRelease(cert); - return status; +//**************************************************************************************************************************************************** +/// \brief Add a certificate to the user's keychain. +/// +/// \param[in] certData The certificate data in DER encoded format. +/// \param[in] certSize The size of the certData in bytes. +/// \return The status for the operation. +//**************************************************************************************************************************************************** +OSStatus addCertificateToKeychain(char const* certData, uint64_t certSize) { + return _addCertificateToKeychain(certFromData(certData, certSize)); +} + +//**************************************************************************************************************************************************** +/// \brief Add a certificate to the user's keychain. +/// +/// \param[in] cert The certificate. +/// \return The status for the operation. +//**************************************************************************************************************************************************** +OSStatus _removeCertificateFromKeychain(SecCertificateRef const cert) { + NSDictionary *query = @{ (id)kSecClass: (id)kSecClassCertificate, + (id)kSecMatchItemList: @[(__bridge id)cert], + (id)kSecMatchLimit: (id)kSecMatchLimitOne, + }; + return SecItemDelete((__bridge CFDictionaryRef) query); +} + +//**************************************************************************************************************************************************** +/// \brief Add a certificate to the user's keychain. +/// +/// \param[in] certData The certificate data in DER encoded format. +/// \param[in] certSize The size of the certData in bytes. +/// \return The status for the operation. +//**************************************************************************************************************************************************** +OSStatus removeCertificateFromKeychain(char const* certData, uint64_t certSize) { + return _removeCertificateFromKeychain(certFromData(certData, certSize)); +} + +//**************************************************************************************************************************************************** +/// \brief Check if a certificate is trusted in the user's keychain. +/// +/// \param[in] cert The certificate. +/// \return true iff the certificate is trusted in the user's keychain. +//**************************************************************************************************************************************************** +bool _isCertificateTrusted(SecCertificateRef const cert) { + CFArrayRef trustSettings = NULL; + OSStatus status = SecTrustSettingsCopyTrustSettings(cert, kSecTrustSettingsDomainUser, &trustSettings); + if (status != errSecSuccess) { + return false; + } + CFIndex count = CFArrayGetCount(trustSettings); + bool result = false; + for (CFIndex index = 0; index < count; ++index) { + CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex(trustSettings, index); + if (!dict) { + continue; + } + CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(dict, kSecTrustSettingsResult); + int value; + if (num && CFNumberGetValue(num, kCFNumberSInt32Type, &value) && (value == kSecTrustSettingsResultTrustRoot)) { + result = true; + break; + } + } + CFRelease(trustSettings); + return result; +} + +//**************************************************************************************************************************************************** +/// \brief Check if a certificate is trusted in the user's keychain. +/// +/// \param[in] certData The certificate data in DER encoded format. +/// \param[in] certSize The size of the certData in bytes. +/// \return true iff the certificate is trusted in the user's keychain. +//**************************************************************************************************************************************************** +bool isCertificateTrusted(char const* certData, uint64_t certSize) { + return _isCertificateTrusted(certFromData(certData, certSize)); +} + +//**************************************************************************************************************************************************** +/// \brief Set the trust level for a certificate in the user's keychain. This call will trigger a security prompt. +/// +/// \param[in] cert The certificate. +/// \param[in] trustLevel The trust level. +/// \return The status for the operation. +//**************************************************************************************************************************************************** +OSStatus _setCertificateTrustLevel(SecCertificateRef const cert, int trustLevel) { + SecPolicyRef policy = SecPolicyCreateSSL(true, NULL); // we limit our trust to SSL + NSDictionary *trustSettings = @{ + (id)kSecTrustSettingsResult: [NSNumber numberWithInt:trustLevel], + (id)kSecTrustSettingsPolicy: (__bridge id) policy, + }; + OSStatus status = SecTrustSettingsSetTrustSettings(cert, kSecTrustSettingsDomainUser, (__bridge CFTypeRef)(trustSettings)); + CFRelease(policy); + return status; +} + +//**************************************************************************************************************************************************** +/// \brief Set a certificate as trusted in the user's keychain. This call will trigger a security prompt. +/// +/// \param[in] cert The certificate. +/// \return The status for the operation. +//**************************************************************************************************************************************************** +OSStatus _setCertificateTrusted(SecCertificateRef cert) { + return _setCertificateTrustLevel(cert, kSecTrustSettingsResultTrustRoot); +} + +//**************************************************************************************************************************************************** +/// \brief Set a certificate as trusted in the user's keychain. This call will trigger a security prompt. +/// +/// \param[in] certData The certificate data in DER encoded format. +/// \param[in] certSize The size of the certData in bytes. +/// \return The status for the operation. +//**************************************************************************************************************************************************** +OSStatus setCertificateTrusted(char const* certData, uint64_t certSize) { + return _setCertificateTrusted(certFromData(certData, certSize)); +} + +//**************************************************************************************************************************************************** +/// \brief Remove the trust level of a certificate in the user's keychain. +/// +/// \param[in] cert The certificate. +/// \return The status for the operation. +//**************************************************************************************************************************************************** +OSStatus _removeCertificateTrust(SecCertificateRef cert) { + return _setCertificateTrustLevel(cert, kSecTrustSettingsResultUnspecified); +} + +//**************************************************************************************************************************************************** +/// \brief Remove the trust level of a certificate in the user's keychain. +/// +/// \param[in] certData The certificate data in DER encoded format. +/// \param[in] certSize The size of the certData in bytes. +/// \return The status for the operation. +//**************************************************************************************************************************************************** +OSStatus removeCertificateTrust(char const* certData, uint64_t certSize) { + return _removeCertificateTrust(certFromData(certData, certSize)); } */ import "C" @@ -105,6 +234,10 @@ const ( errAuthorizationCanceled = -60006 ) +var ( + ErrUserCanceledCertificateInstall = errors.New("the user cancelled the authorization dialog") +) + // certPEMToDER converts a certificate in PEM format to DER format, which is the format required by Apple's Security framework. func certPEMToDER(certPEM []byte) ([]byte, error) { block, left := pem.Decode(certPEM) @@ -119,6 +252,116 @@ func certPEMToDER(certPEM []byte) ([]byte, error) { return block.Bytes, nil } +// wrapCGoCertCallReturningBool wrap call to a CGo function returning a bool. +// if the certificate is invalid the call will return false. +func wrapCGoCertCallReturningBool(certPEM []byte, fn func(*C.char, C.ulonglong) bool) bool { + certDER, err := certPEMToDER(certPEM) + if err != nil { + return false // error are ignored + } + + buffer := C.CBytes(certDER) + defer C.free(unsafe.Pointer(buffer)) //nolint:unconvert + + return fn((*C.char)(buffer), C.ulonglong(len(certDER))) +} + +// wrapCGoCertCallReturningBool wrap call to a CGo function returning an error +func wrapCGoCertCallReturningError(certPEM []byte, fn func(*C.char, C.ulonglong) error) error { + certDER, err := certPEMToDER(certPEM) + if err != nil { + return err + } + + buffer := C.CBytes(certDER) + defer C.free(unsafe.Pointer(buffer)) //nolint:unconvert + + return fn((*C.char)(buffer), C.ulonglong(len(certDER))) +} + +// isCertInKeychain returns true if the given certificate is stored in the user's keychain. +func isCertInKeychain(certPEM []byte) bool { + return wrapCGoCertCallReturningBool(certPEM, isCertInKeychainCGo) +} + +func isCertInKeychainCGo(buffer *C.char, size C.ulonglong) bool { + return bool(C.isCertificateInKeychain(buffer, size)) +} + +// addCertToKeychain adds a certificate to the user's keychain. +// Trying to add a certificate that is already in the keychain will result in an error. +func addCertToKeychain(certPEM []byte) error { + return wrapCGoCertCallReturningError(certPEM, addCertToKeychainCGo) +} + +func addCertToKeychainCGo(buffer *C.char, size C.ulonglong) error { + if errCode := C.addCertificateToKeychain(buffer, size); errCode != errSecSuccess { + return fmt.Errorf("could not add certificate to keychain (error %v)", errCode) + } + + return nil +} + +// removeCertFromKeychain removes a certificate from the user's keychain. +// Trying to remove a certificate that is not in the keychain will result in an error. +func removeCertFromKeychain(certPEM []byte) error { + return wrapCGoCertCallReturningError(certPEM, removeCertFromKeychainCGo) +} + +func removeCertFromKeychainCGo(buffer *C.char, size C.ulonglong) error { + if errCode := C.removeCertificateFromKeychain(buffer, size); errCode != errSecSuccess { + return fmt.Errorf("could not remove certificate from keychain (error %v)", errCode) + } + return nil +} + +// isCertTrusted check if a certificate is trusted in the user's keychain. +func isCertTrusted(certPEM []byte) bool { + return wrapCGoCertCallReturningBool(certPEM, isCertTrustedCGo) +} + +func isCertTrustedCGo(buffer *C.char, size C.ulonglong) bool { + return bool(C.isCertificateTrusted(buffer, size)) +} + +// setCertTrusted sets a certificate as trusted in the user's keychain. +// This function will trigger a security prompt from the system. +func setCertTrusted(certPEM []byte) error { + return wrapCGoCertCallReturningError(certPEM, setCertTrustedCGo) +} + +func setCertTrustedCGo(buffer *C.char, size C.ulonglong) error { + errCode := C.setCertificateTrusted(buffer, size) + switch errCode { + case errSecSuccess: + return nil + case errAuthorizationCanceled: + return ErrUserCanceledCertificateInstall + default: + return fmt.Errorf("could not set certificate trust in keychain (error %v)", errCode) + } +} + +// removeCertTrust remove the trust level of the certificated from the user's keychain. +// This function will trigger a security prompt from the system. +func removeCertTrust(certPEM []byte) error { + return wrapCGoCertCallReturningError(certPEM, removeCertTrustCGo) +} + +func removeCertTrustCGo(buffer *C.char, size C.ulonglong) error { + errCode := C.removeCertificateTrust(buffer, size) + switch errCode { + case errSecSuccess: + return nil + case errAuthorizationCanceled: + return ErrUserCanceledCertificateInstall + default: + return fmt.Errorf("could not set certificate trust in keychain (error %v)", errCode) + } +} + +// installCert installs a certificate in the keychain. The certificate is added to the keychain and it is set as trusted. +// This function will trigger a security prompt from the system, unless the certificate is already trusted in the user keychain. func installCert(certPEM []byte) error { certDER, err := certPEMToDER(certPEM) if err != nil { @@ -127,18 +370,24 @@ func installCert(certPEM []byte) error { p := C.CBytes(certDER) defer C.free(unsafe.Pointer(p)) //nolint:unconvert + buffer := (*C.char)(p) + size := C.ulonglong(len(certDER)) - errCode := C.installTrustedCert((*C.char)(p), (C.ulonglong)(len(certDER))) - switch errCode { - case errSecSuccess: - return nil - case errAuthorizationCanceled: - return fmt.Errorf("the user cancelled the authorization dialog") - default: - return fmt.Errorf("could not install certification into keychain (error %v)", errCode) + if !isCertInKeychainCGo(buffer, size) { + if err := addCertToKeychainCGo(buffer, size); err != nil { + return err + } } + + if !isCertTrustedCGo(buffer, size) { + return setCertTrustedCGo(buffer, size) + } + + return nil } +// uninstallCert uninstalls a certificate in the keychain. The certificate trust is removed and the certificated is deleted from the keychain. +// This function will trigger a security prompt from the system, unless the certificate is not trusted in the user keychain. func uninstallCert(certPEM []byte) error { certDER, err := certPEMToDER(certPEM) if err != nil { @@ -147,10 +396,32 @@ func uninstallCert(certPEM []byte) error { p := C.CBytes(certDER) defer C.free(unsafe.Pointer(p)) //nolint:unconvert + buffer := (*C.char)(p) + size := C.ulonglong(len(certDER)) - if errCode := C.removeTrustedCert((*C.char)(p), (C.ulonglong)(len(certDER))); errCode != 0 { - return fmt.Errorf("could not install certificate from keychain (error %v)", errCode) + if isCertTrustedCGo(buffer, size) { + if err := removeCertTrustCGo(buffer, size); err != nil { + return err + } + } + + if isCertInKeychainCGo(buffer, size) { + return removeCertFromKeychainCGo(buffer, size) } return nil } + +func isCertInstalled(certPEM []byte) bool { + certDER, err := certPEMToDER(certPEM) + if err != nil { + return false + } + + p := C.CBytes(certDER) + defer C.free(unsafe.Pointer(p)) //nolint:unconvert + buffer := (*C.char)(p) + size := C.ulonglong(len(certDER)) + + return isCertInKeychainCGo(buffer, size) && isCertTrustedCGo(buffer, size) +} diff --git a/internal/certs/cert_store_darwin_test.go b/internal/certs/cert_store_darwin_test.go index 3b1d419f..2b7dda59 100644 --- a/internal/certs/cert_store_darwin_test.go +++ b/internal/certs/cert_store_darwin_test.go @@ -25,20 +25,73 @@ import ( "github.com/stretchr/testify/require" ) -// This test implies human interactions to enter password and is disabled by default. -func _TestTrustedCertsDarwin(t *testing.T) { //nolint:unused +func TestCertInKeychain(t *testing.T) { + // no trust settings change is performed, so this test will not trigger an OS security prompt. + certPEM := generatePEMCertificate(t) + require.False(t, isCertInKeychain(certPEM)) + require.NoError(t, addCertToKeychain(certPEM)) + require.True(t, isCertInKeychain(certPEM)) + require.Error(t, addCertToKeychain(certPEM)) + require.True(t, isCertInKeychain(certPEM)) + require.NoError(t, removeCertFromKeychain(certPEM)) + require.False(t, isCertInKeychain(certPEM)) + require.Error(t, removeCertFromKeychain(certPEM)) + require.False(t, isCertInKeychain(certPEM)) +} + +// This test require human interaction (macOS security prompts), and is disabled by default. +func TestCertificateTrust(t *testing.T) { + certPEM := generatePEMCertificate(t) + require.False(t, isCertTrusted(certPEM)) + require.NoError(t, addCertToKeychain(certPEM)) + require.NoError(t, setCertTrusted(certPEM)) + require.True(t, isCertTrusted(certPEM)) + require.NoError(t, removeCertTrust(certPEM)) + require.False(t, isCertTrusted(certPEM)) + require.NoError(t, removeCertFromKeychain(certPEM)) +} + +// This test require human interaction (macOS security prompts), and is disabled by default. +func TestInstallAndRemove(t *testing.T) { + certPEM := generatePEMCertificate(t) + + // fresh install + require.False(t, isCertInstalled(certPEM)) + require.NoError(t, installCert(certPEM)) + require.True(t, isCertInKeychain(certPEM)) + require.True(t, isCertTrusted(certPEM)) + require.True(t, isCertInstalled(certPEM)) + require.NoError(t, uninstallCert(certPEM)) + require.False(t, isCertInKeychain(certPEM)) + require.False(t, isCertTrusted(certPEM)) + require.False(t, isCertInstalled(certPEM)) + + // Install where certificate is already in Keychain, but not trusted. + require.NoError(t, addCertToKeychain(certPEM)) + require.False(t, isCertInstalled(certPEM)) + require.NoError(t, installCert(certPEM)) + require.True(t, isCertInstalled(certPEM)) + + // Install where certificate is already installed + require.NoError(t, installCert(certPEM)) + + // Remove when certificate is not trusted. + require.NoError(t, removeCertTrust(certPEM)) + require.NoError(t, uninstallCert(certPEM)) + require.False(t, isCertInstalled(certPEM)) + + // Remove when certificate has already been removed. + require.NoError(t, uninstallCert(certPEM)) + require.False(t, isCertTrusted(certPEM)) + require.False(t, isCertInKeychain(certPEM)) +} + +func generatePEMCertificate(t *testing.T) []byte { template, err := NewTLSTemplate() require.NoError(t, err) certPEM, _, err := GenerateCert(template) require.NoError(t, err) - require.Error(t, installCert([]byte{0})) // Cannot install an invalid cert. - require.Error(t, uninstallCert(certPEM)) // Cannot uninstall a cert that is not installed. - require.NoError(t, installCert(certPEM)) // Can install a valid cert. - require.NoError(t, installCert(certPEM)) // Can install an already installed cert. - require.NoError(t, uninstallCert(certPEM)) // Can uninstall an installed cert. - require.Error(t, uninstallCert(certPEM)) // Cannot uninstall an already uninstalled cert. - require.NoError(t, installCert(certPEM)) // Can reinstall an uninstalled cert. - require.NoError(t, uninstallCert(certPEM)) // Can uninstall a reinstalled cert. + return certPEM } diff --git a/internal/certs/installer.go b/internal/certs/installer.go index 25f09e3d..fd14054f 100644 --- a/internal/certs/installer.go +++ b/internal/certs/installer.go @@ -30,3 +30,7 @@ func (installer *Installer) InstallCert(certPEM []byte) error { func (installer *Installer) UninstallCert(certPEM []byte) error { return uninstallCert(certPEM) } + +func (installer *Installer) IsCertInstalled(certPEM []byte) bool { + return isCertInstalled(certPEM) +} From f57a40677e9581b24364fbed9fb1547608d3a6d9 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Thu, 17 Aug 2023 10:22:56 +0200 Subject: [PATCH 31/93] feat(GODT-2771): gRPC calls for TLS certificates. --- internal/certs/cert_store_linux.go | 4 + internal/certs/cert_store_windows.go | 4 + .../bridge-gui/bridge-gui/QMLBackend.cpp | 25 + .../bridge-gui/bridge-gui/QMLBackend.h | 5 + .../bridgepp/bridgepp/GRPC/EventFactory.cpp | 33 + .../bridgepp/bridgepp/GRPC/EventFactory.h | 3 + .../bridgepp/bridgepp/GRPC/GRPCClient.cpp | 47 +- .../bridgepp/bridgepp/GRPC/GRPCClient.h | 9 +- internal/frontend/grpc/bridge.pb.go | 2250 +++++++++-------- internal/frontend/grpc/bridge.proto | 12 +- internal/frontend/grpc/bridge_grpc.pb.go | 146 +- internal/frontend/grpc/event_factory.go | 12 + internal/frontend/grpc/service_cert.go | 79 + internal/frontend/grpc/service_methods.go | 22 - 14 files changed, 1578 insertions(+), 1073 deletions(-) create mode 100644 internal/frontend/grpc/service_cert.go diff --git a/internal/certs/cert_store_linux.go b/internal/certs/cert_store_linux.go index ed66925b..16c4ff3f 100644 --- a/internal/certs/cert_store_linux.go +++ b/internal/certs/cert_store_linux.go @@ -24,3 +24,7 @@ func installCert([]byte) error { func uninstallCert([]byte) error { return nil // Linux doesn't have a root cert store. } + +func isCertInstalled([]byte) error { + return false +} diff --git a/internal/certs/cert_store_windows.go b/internal/certs/cert_store_windows.go index 797559ad..cb6f19e3 100644 --- a/internal/certs/cert_store_windows.go +++ b/internal/certs/cert_store_windows.go @@ -24,3 +24,7 @@ func installCert([]byte) error { func uninstallCert([]byte) error { return nil // NOTE(GODT-986): Uninstall certs from root cert store? } + +func isCertInstalled([]byte) error { + return false +} diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp index a3984ca9..de9c189f 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp @@ -278,6 +278,28 @@ void QMLBackend::clearAnswers() { } +//**************************************************************************************************************************************************** +/// \return true iff the Bridge TLS certificate is installed. +//**************************************************************************************************************************************************** +bool QMLBackend::isTLSCertificateInstalled() { + HANDLE_EXCEPTION_RETURN_BOOL( + bool v = false; + app().grpc().isTLSCertificateInstalled(v); + return v; + ) +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void QMLBackend::installTLSCertificate() { + HANDLE_EXCEPTION( + app().grpc().installTLSCertificate(); + ) +} + + //**************************************************************************************************************************************************** /// \return The value for the 'showOnStartup' property. //**************************************************************************************************************************************************** @@ -1267,6 +1289,9 @@ void QMLBackend::connectGrpcEvents() { connect(client, &GRPCClient::reportBugSuccess, this, &QMLBackend::bugReportSendSuccess); connect(client, &GRPCClient::reportBugFallback, this, &QMLBackend::bugReportSendFallback); connect(client, &GRPCClient::reportBugError, this, &QMLBackend::bugReportSendError); + connect(client, &GRPCClient::certificateInstallSuccess, this, &QMLBackend::certificateInstallSuccess); + connect(client, &GRPCClient::certificateInstallCanceled, this, &QMLBackend::certificateInstallCanceled); + connect(client, &GRPCClient::certificateInstallFailed, this, &QMLBackend::certificateInstallFailed); connect(client, &GRPCClient::showMainWindow, [&]() { this->showMainWindow("gRPC showMainWindow event"); }); // cache events diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h index 0f086f7e..c8b80df8 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h @@ -64,6 +64,8 @@ public: // member functions. Q_INVOKABLE QString getQuestionAnswer(quint8 questionId) const; ///< Get the answer for a given question. Q_INVOKABLE QString collectAnswers(quint8 categoryId) const; ///< Collect answer for a given set of questions. Q_INVOKABLE void clearAnswers(); ///< Clear all collected answers. + Q_INVOKABLE bool isTLSCertificateInstalled(); ///< Check if the bridge certificate is installed in the OS keychain. + Q_INVOKABLE void installTLSCertificate(); ///< Installs the Bridge TLS certificate in the Keychain. public: // Qt/QML properties. Note that the NOTIFY-er signal is required even for read-only properties (QML warning otherwise) Q_PROPERTY(bool showOnStartup READ showOnStartup NOTIFY showOnStartupChanged) @@ -268,6 +270,9 @@ signals: // Signals received from the Go backend, to be forwarded to QML void bugReportSendSuccess(); ///< Signal for the 'bugReportSendSuccess' gRPC stream event. void bugReportSendFallback(); ///< Signal for the 'bugReportSendFallback' gRPC stream event. void bugReportSendError(); ///< Signal for the 'bugReportSendError' gRPC stream event. + void certificateInstallSuccess(); ///< Signal for the 'certificateInstallSuccess' gRPC stream event. + void certificateInstallCanceled(); ///< Signal for the 'certificateInstallCanceled' gRPC stream event. + void certificateInstallFailed(); /// Signal for the 'certificateInstallFailed' gRPC stream event. void showMainWindow(); ///< Signal for the 'showMainWindow' gRPC stream event. void hideMainWindow(); ///< Signal for the 'hideMainWindow' gRPC stream event. void showHelp(); ///< Signal for the 'showHelp' event (from the context menu). diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.cpp index 7457e17e..6780d171 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.cpp @@ -202,6 +202,39 @@ SPStreamEvent newReportBugErrorEvent() { } +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newCertificateInstallSuccessEvent() { + auto event = new grpc::CertificateInstallSuccessEvent; + auto appEvent = new grpc::AppEvent; + appEvent->set_allocated_certificateinstallsuccess(event); + return wrapAppEvent(appEvent); +} + + +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newCertificateInstallCanceledEvent() { + auto event = new grpc::CertificateInstallCanceledEvent; + auto appEvent = new grpc::AppEvent; + appEvent->set_allocated_certificateinstallcanceled(event); + return wrapAppEvent(appEvent); +} + + +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newCertificateInstallFailedEvent() { + auto event = new grpc::CertificateInstallFailedEvent; + auto appEvent = new grpc::AppEvent; + appEvent->set_allocated_certificateinstallfailed(event); + return wrapAppEvent(appEvent); +} + + //**************************************************************************************************************************************************** /// \return The event. //**************************************************************************************************************************************************** diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.h index 44a6deae..1c2d31b9 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.h @@ -34,6 +34,9 @@ SPStreamEvent newResetFinishedEvent(); ///< Create a new ResetFinishedEvent even SPStreamEvent newReportBugFinishedEvent(); ///< Create a new ReportBugFinishedEvent event. SPStreamEvent newReportBugSuccessEvent(); ///< Create a new ReportBugSuccessEvent event. SPStreamEvent newReportBugErrorEvent(); ///< Create a new ReportBugErrorEvent event. +SPStreamEvent newCertificateInstallSuccessEvent(); ///< Create a new CertificateInstallSuccessEvent event. +SPStreamEvent newCertificateInstallCanceledEvent(); ///< Create a new CertificateInstallCanceledEvent event. +SPStreamEvent newCertificateInstallFailedEvent(); ///< Create anew CertificateInstallFailedEvent event. SPStreamEvent newShowMainWindowEvent(); ///< Create a new ShowMainWindowEvent event. // Login events diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp index 767c184d..a43b4bcc 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp @@ -373,14 +373,6 @@ grpc::Status GRPCClient::reportBug(QString const &category, QString const &descr } -//**************************************************************************************************************************************************** -/// \param[in] folderPath of the folder where the TLS files should be stored. -//**************************************************************************************************************************************************** -grpc::Status GRPCClient::exportTLSCertificates(QString const &folderPath) { - return this->logGRPCCallStatus(this->setString(&Bridge::Stub::ExportTLSCertificates, folderPath), __FUNCTION__); -} - - //**************************************************************************************************************************************************** /// \param[out] outIMAPPort The IMAP port. /// \param[out] outSMTPPort The SMTP port. @@ -811,6 +803,32 @@ grpc::Status GRPCClient::setCurrentKeychain(QString const &keychain) { } +//**************************************************************************************************************************************************** +/// \param[out] isInstalled is The Bridge certificate installed in the keychain. +/// \return The status for the call +//**************************************************************************************************************************************************** +grpc::Status GRPCClient::isTLSCertificateInstalled(bool isInstalled) { + return this->logGRPCCallStatus(this->getBool(&Bridge::Stub::IsTLSCertificateInstalled, isInstalled), __FUNCTION__); +} + + +//**************************************************************************************************************************************************** +/// \return The status for the gRPC call. +//**************************************************************************************************************************************************** +grpc::Status GRPCClient::installTLSCertificate() { + return this->logGRPCCallStatus(this->simpleMethod(&Bridge::Stub::InstallTLSCertificate), __FUNCTION__); +} + + +//**************************************************************************************************************************************************** +/// \param[in] folderPath of the folder where the TLS files should be stored. +/// \return The status for the gRPC call. +//**************************************************************************************************************************************************** +grpc::Status GRPCClient::exportTLSCertificates(QString const &folderPath) { + return this->logGRPCCallStatus(this->setString(&Bridge::Stub::ExportTLSCertificates, folderPath), __FUNCTION__); +} + + //**************************************************************************************************************************************************** /// \return true iff the event stream is active. //**************************************************************************************************************************************************** @@ -1134,6 +1152,18 @@ void GRPCClient::processAppEvent(AppEvent const &event) { this->logTrace("App event received: ReportBugFallback."); emit reportBugFallback(); break; + case AppEvent::kCertificateInstallSuccess: + this->logTrace("App event received: CertificateInstallSuccess."); + emit certificateInstallSuccess(); + break; + case AppEvent::kCertificateInstallCanceled: + this->logTrace("App event received: CertificateInstallCanceled."); + emit certificateInstallCanceled(); + break; + case AppEvent::kCertificateInstallFailed: + this->logTrace("App event received: CertificateInstallFailed."); + emit certificateInstallFailed(); + break; default: this->logError("Unknown App event received."); } @@ -1517,4 +1547,5 @@ grpc::Status GRPCClient::KBArticleClicked(QString const &article) { return this->logGRPCCallStatus(stub_->KBArticleClicked(this->clientContext().get(), s, &empty), __FUNCTION__); } + } // namespace bridgepp diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h index af3f8c16..d2977a57 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h @@ -78,7 +78,6 @@ public: // member functions. grpc::Status setColorSchemeName(QString const &name); ///< Performs the "setColorSchemeName' gRPC call. grpc::Status currentEmailClient(QString &outName); ///< Performs the 'currentEmailClient' gRPC call. grpc::Status reportBug(QString const &category, QString const &description, QString const &address, QString const &emailClient, bool includeLogs); ///< Performs the 'ReportBug' gRPC call. - grpc::Status exportTLSCertificates(QString const &folderPath); ///< Performs the 'ExportTLSCertificates' gRPC call. grpc::Status quit(); ///< Perform the "Quit" gRPC call. grpc::Status restart(); ///< Performs the Restart gRPC call. grpc::Status triggerReset(); ///< Performs the triggerReset gRPC call. @@ -103,6 +102,9 @@ signals: // app related signals void reportBugSuccess(); void reportBugError(); void reportBugFallback(); + void certificateInstallSuccess(); + void certificateInstallCanceled(); + void certificateInstallFailed(); void showMainWindow(); // cache related calls @@ -201,6 +203,11 @@ public: // keychain related calls grpc::Status currentKeychain(QString &outKeychain); grpc::Status setCurrentKeychain(QString const &keychain); +public: // cert related calls + grpc::Status isTLSCertificateInstalled(bool isInstalled); ///< Perform the 'IsTLSCertificateInstalled' gRPC call. + grpc::Status installTLSCertificate(); ///< Perform the 'InstallTLSCertificate' gRPC call. + grpc::Status exportTLSCertificates(QString const &folderPath); ///< Performs the 'ExportTLSCertificates' gRPC call. + signals: void changeKeychainFinished(); void hasNoKeychain(); diff --git a/internal/frontend/grpc/bridge.pb.go b/internal/frontend/grpc/bridge.pb.go index 357fd816..d43c4f64 100644 --- a/internal/frontend/grpc/bridge.pb.go +++ b/internal/frontend/grpc/bridge.pb.go @@ -1427,6 +1427,9 @@ type AppEvent struct { // *AppEvent_ReportBugError // *AppEvent_ShowMainWindow // *AppEvent_ReportBugFallback + // *AppEvent_CertificateInstallSuccess + // *AppEvent_CertificateInstallCanceled + // *AppEvent_CertificateInstallFailed Event isAppEvent_Event `protobuf_oneof:"event"` } @@ -1525,6 +1528,27 @@ func (x *AppEvent) GetReportBugFallback() *ReportBugFallbackEvent { return nil } +func (x *AppEvent) GetCertificateInstallSuccess() *CertificateInstallSuccessEvent { + if x, ok := x.GetEvent().(*AppEvent_CertificateInstallSuccess); ok { + return x.CertificateInstallSuccess + } + return nil +} + +func (x *AppEvent) GetCertificateInstallCanceled() *CertificateInstallCanceledEvent { + if x, ok := x.GetEvent().(*AppEvent_CertificateInstallCanceled); ok { + return x.CertificateInstallCanceled + } + return nil +} + +func (x *AppEvent) GetCertificateInstallFailed() *CertificateInstallFailedEvent { + if x, ok := x.GetEvent().(*AppEvent_CertificateInstallFailed); ok { + return x.CertificateInstallFailed + } + return nil +} + type isAppEvent_Event interface { isAppEvent_Event() } @@ -1561,6 +1585,18 @@ type AppEvent_ReportBugFallback struct { ReportBugFallback *ReportBugFallbackEvent `protobuf:"bytes,8,opt,name=reportBugFallback,proto3,oneof"` } +type AppEvent_CertificateInstallSuccess struct { + CertificateInstallSuccess *CertificateInstallSuccessEvent `protobuf:"bytes,9,opt,name=certificateInstallSuccess,proto3,oneof"` +} + +type AppEvent_CertificateInstallCanceled struct { + CertificateInstallCanceled *CertificateInstallCanceledEvent `protobuf:"bytes,10,opt,name=certificateInstallCanceled,proto3,oneof"` +} + +type AppEvent_CertificateInstallFailed struct { + CertificateInstallFailed *CertificateInstallFailedEvent `protobuf:"bytes,11,opt,name=certificateInstallFailed,proto3,oneof"` +} + func (*AppEvent_InternetStatus) isAppEvent_Event() {} func (*AppEvent_ToggleAutostartFinished) isAppEvent_Event() {} @@ -1577,6 +1613,12 @@ func (*AppEvent_ShowMainWindow) isAppEvent_Event() {} func (*AppEvent_ReportBugFallback) isAppEvent_Event() {} +func (*AppEvent_CertificateInstallSuccess) isAppEvent_Event() {} + +func (*AppEvent_CertificateInstallCanceled) isAppEvent_Event() {} + +func (*AppEvent_CertificateInstallFailed) isAppEvent_Event() {} + type InternetStatusEvent struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1890,6 +1932,120 @@ func (*ReportBugFallbackEvent) Descriptor() ([]byte, []int) { return file_bridge_proto_rawDescGZIP(), []int{22} } +type CertificateInstallSuccessEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *CertificateInstallSuccessEvent) Reset() { + *x = CertificateInstallSuccessEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_bridge_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CertificateInstallSuccessEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CertificateInstallSuccessEvent) ProtoMessage() {} + +func (x *CertificateInstallSuccessEvent) ProtoReflect() protoreflect.Message { + mi := &file_bridge_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CertificateInstallSuccessEvent.ProtoReflect.Descriptor instead. +func (*CertificateInstallSuccessEvent) Descriptor() ([]byte, []int) { + return file_bridge_proto_rawDescGZIP(), []int{23} +} + +type CertificateInstallCanceledEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *CertificateInstallCanceledEvent) Reset() { + *x = CertificateInstallCanceledEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_bridge_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CertificateInstallCanceledEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CertificateInstallCanceledEvent) ProtoMessage() {} + +func (x *CertificateInstallCanceledEvent) ProtoReflect() protoreflect.Message { + mi := &file_bridge_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CertificateInstallCanceledEvent.ProtoReflect.Descriptor instead. +func (*CertificateInstallCanceledEvent) Descriptor() ([]byte, []int) { + return file_bridge_proto_rawDescGZIP(), []int{24} +} + +type CertificateInstallFailedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *CertificateInstallFailedEvent) Reset() { + *x = CertificateInstallFailedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_bridge_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CertificateInstallFailedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CertificateInstallFailedEvent) ProtoMessage() {} + +func (x *CertificateInstallFailedEvent) ProtoReflect() protoreflect.Message { + mi := &file_bridge_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CertificateInstallFailedEvent.ProtoReflect.Descriptor instead. +func (*CertificateInstallFailedEvent) Descriptor() ([]byte, []int) { + return file_bridge_proto_rawDescGZIP(), []int{25} +} + //********************************************************** // Login related events //********************************************************** @@ -1910,7 +2066,7 @@ type LoginEvent struct { func (x *LoginEvent) Reset() { *x = LoginEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[23] + mi := &file_bridge_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1923,7 +2079,7 @@ func (x *LoginEvent) String() string { func (*LoginEvent) ProtoMessage() {} func (x *LoginEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[23] + mi := &file_bridge_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1936,7 +2092,7 @@ func (x *LoginEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginEvent.ProtoReflect.Descriptor instead. func (*LoginEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{23} + return file_bridge_proto_rawDescGZIP(), []int{26} } func (m *LoginEvent) GetEvent() isLoginEvent_Event { @@ -2027,7 +2183,7 @@ type LoginErrorEvent struct { func (x *LoginErrorEvent) Reset() { *x = LoginErrorEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[24] + mi := &file_bridge_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2040,7 +2196,7 @@ func (x *LoginErrorEvent) String() string { func (*LoginErrorEvent) ProtoMessage() {} func (x *LoginErrorEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[24] + mi := &file_bridge_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2053,7 +2209,7 @@ func (x *LoginErrorEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginErrorEvent.ProtoReflect.Descriptor instead. func (*LoginErrorEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{24} + return file_bridge_proto_rawDescGZIP(), []int{27} } func (x *LoginErrorEvent) GetType() LoginErrorType { @@ -2081,7 +2237,7 @@ type LoginTfaRequestedEvent struct { func (x *LoginTfaRequestedEvent) Reset() { *x = LoginTfaRequestedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[25] + mi := &file_bridge_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2094,7 +2250,7 @@ func (x *LoginTfaRequestedEvent) String() string { func (*LoginTfaRequestedEvent) ProtoMessage() {} func (x *LoginTfaRequestedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[25] + mi := &file_bridge_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2107,7 +2263,7 @@ func (x *LoginTfaRequestedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginTfaRequestedEvent.ProtoReflect.Descriptor instead. func (*LoginTfaRequestedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{25} + return file_bridge_proto_rawDescGZIP(), []int{28} } func (x *LoginTfaRequestedEvent) GetUsername() string { @@ -2126,7 +2282,7 @@ type LoginTwoPasswordsRequestedEvent struct { func (x *LoginTwoPasswordsRequestedEvent) Reset() { *x = LoginTwoPasswordsRequestedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[26] + mi := &file_bridge_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2139,7 +2295,7 @@ func (x *LoginTwoPasswordsRequestedEvent) String() string { func (*LoginTwoPasswordsRequestedEvent) ProtoMessage() {} func (x *LoginTwoPasswordsRequestedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[26] + mi := &file_bridge_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2152,7 +2308,7 @@ func (x *LoginTwoPasswordsRequestedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginTwoPasswordsRequestedEvent.ProtoReflect.Descriptor instead. func (*LoginTwoPasswordsRequestedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{26} + return file_bridge_proto_rawDescGZIP(), []int{29} } type LoginFinishedEvent struct { @@ -2167,7 +2323,7 @@ type LoginFinishedEvent struct { func (x *LoginFinishedEvent) Reset() { *x = LoginFinishedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[27] + mi := &file_bridge_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2180,7 +2336,7 @@ func (x *LoginFinishedEvent) String() string { func (*LoginFinishedEvent) ProtoMessage() {} func (x *LoginFinishedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[27] + mi := &file_bridge_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2193,7 +2349,7 @@ func (x *LoginFinishedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginFinishedEvent.ProtoReflect.Descriptor instead. func (*LoginFinishedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{27} + return file_bridge_proto_rawDescGZIP(), []int{30} } func (x *LoginFinishedEvent) GetUserID() string { @@ -2233,7 +2389,7 @@ type UpdateEvent struct { func (x *UpdateEvent) Reset() { *x = UpdateEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[28] + mi := &file_bridge_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2246,7 +2402,7 @@ func (x *UpdateEvent) String() string { func (*UpdateEvent) ProtoMessage() {} func (x *UpdateEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[28] + mi := &file_bridge_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2259,7 +2415,7 @@ func (x *UpdateEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateEvent.ProtoReflect.Descriptor instead. func (*UpdateEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{28} + return file_bridge_proto_rawDescGZIP(), []int{31} } func (m *UpdateEvent) GetEvent() isUpdateEvent_Event { @@ -2388,7 +2544,7 @@ type UpdateErrorEvent struct { func (x *UpdateErrorEvent) Reset() { *x = UpdateErrorEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[29] + mi := &file_bridge_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2401,7 +2557,7 @@ func (x *UpdateErrorEvent) String() string { func (*UpdateErrorEvent) ProtoMessage() {} func (x *UpdateErrorEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[29] + mi := &file_bridge_proto_msgTypes[32] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2414,7 +2570,7 @@ func (x *UpdateErrorEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateErrorEvent.ProtoReflect.Descriptor instead. func (*UpdateErrorEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{29} + return file_bridge_proto_rawDescGZIP(), []int{32} } func (x *UpdateErrorEvent) GetType() UpdateErrorType { @@ -2435,7 +2591,7 @@ type UpdateManualReadyEvent struct { func (x *UpdateManualReadyEvent) Reset() { *x = UpdateManualReadyEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[30] + mi := &file_bridge_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2448,7 +2604,7 @@ func (x *UpdateManualReadyEvent) String() string { func (*UpdateManualReadyEvent) ProtoMessage() {} func (x *UpdateManualReadyEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[30] + mi := &file_bridge_proto_msgTypes[33] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2461,7 +2617,7 @@ func (x *UpdateManualReadyEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateManualReadyEvent.ProtoReflect.Descriptor instead. func (*UpdateManualReadyEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{30} + return file_bridge_proto_rawDescGZIP(), []int{33} } func (x *UpdateManualReadyEvent) GetVersion() string { @@ -2480,7 +2636,7 @@ type UpdateManualRestartNeededEvent struct { func (x *UpdateManualRestartNeededEvent) Reset() { *x = UpdateManualRestartNeededEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[31] + mi := &file_bridge_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2493,7 +2649,7 @@ func (x *UpdateManualRestartNeededEvent) String() string { func (*UpdateManualRestartNeededEvent) ProtoMessage() {} func (x *UpdateManualRestartNeededEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[31] + mi := &file_bridge_proto_msgTypes[34] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2506,7 +2662,7 @@ func (x *UpdateManualRestartNeededEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateManualRestartNeededEvent.ProtoReflect.Descriptor instead. func (*UpdateManualRestartNeededEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{31} + return file_bridge_proto_rawDescGZIP(), []int{34} } type UpdateForceEvent struct { @@ -2520,7 +2676,7 @@ type UpdateForceEvent struct { func (x *UpdateForceEvent) Reset() { *x = UpdateForceEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[32] + mi := &file_bridge_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2533,7 +2689,7 @@ func (x *UpdateForceEvent) String() string { func (*UpdateForceEvent) ProtoMessage() {} func (x *UpdateForceEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[32] + mi := &file_bridge_proto_msgTypes[35] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2546,7 +2702,7 @@ func (x *UpdateForceEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateForceEvent.ProtoReflect.Descriptor instead. func (*UpdateForceEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{32} + return file_bridge_proto_rawDescGZIP(), []int{35} } func (x *UpdateForceEvent) GetVersion() string { @@ -2565,7 +2721,7 @@ type UpdateSilentRestartNeeded struct { func (x *UpdateSilentRestartNeeded) Reset() { *x = UpdateSilentRestartNeeded{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[33] + mi := &file_bridge_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2578,7 +2734,7 @@ func (x *UpdateSilentRestartNeeded) String() string { func (*UpdateSilentRestartNeeded) ProtoMessage() {} func (x *UpdateSilentRestartNeeded) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[33] + mi := &file_bridge_proto_msgTypes[36] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2591,7 +2747,7 @@ func (x *UpdateSilentRestartNeeded) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateSilentRestartNeeded.ProtoReflect.Descriptor instead. func (*UpdateSilentRestartNeeded) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{33} + return file_bridge_proto_rawDescGZIP(), []int{36} } type UpdateIsLatestVersion struct { @@ -2603,7 +2759,7 @@ type UpdateIsLatestVersion struct { func (x *UpdateIsLatestVersion) Reset() { *x = UpdateIsLatestVersion{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[34] + mi := &file_bridge_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2616,7 +2772,7 @@ func (x *UpdateIsLatestVersion) String() string { func (*UpdateIsLatestVersion) ProtoMessage() {} func (x *UpdateIsLatestVersion) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[34] + mi := &file_bridge_proto_msgTypes[37] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2629,7 +2785,7 @@ func (x *UpdateIsLatestVersion) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateIsLatestVersion.ProtoReflect.Descriptor instead. func (*UpdateIsLatestVersion) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{34} + return file_bridge_proto_rawDescGZIP(), []int{37} } type UpdateCheckFinished struct { @@ -2641,7 +2797,7 @@ type UpdateCheckFinished struct { func (x *UpdateCheckFinished) Reset() { *x = UpdateCheckFinished{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[35] + mi := &file_bridge_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2654,7 +2810,7 @@ func (x *UpdateCheckFinished) String() string { func (*UpdateCheckFinished) ProtoMessage() {} func (x *UpdateCheckFinished) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[35] + mi := &file_bridge_proto_msgTypes[38] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2667,7 +2823,7 @@ func (x *UpdateCheckFinished) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateCheckFinished.ProtoReflect.Descriptor instead. func (*UpdateCheckFinished) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{35} + return file_bridge_proto_rawDescGZIP(), []int{38} } type UpdateVersionChanged struct { @@ -2679,7 +2835,7 @@ type UpdateVersionChanged struct { func (x *UpdateVersionChanged) Reset() { *x = UpdateVersionChanged{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[36] + mi := &file_bridge_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2692,7 +2848,7 @@ func (x *UpdateVersionChanged) String() string { func (*UpdateVersionChanged) ProtoMessage() {} func (x *UpdateVersionChanged) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[36] + mi := &file_bridge_proto_msgTypes[39] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2705,7 +2861,7 @@ func (x *UpdateVersionChanged) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateVersionChanged.ProtoReflect.Descriptor instead. func (*UpdateVersionChanged) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{36} + return file_bridge_proto_rawDescGZIP(), []int{39} } //********************************************************** @@ -2726,7 +2882,7 @@ type DiskCacheEvent struct { func (x *DiskCacheEvent) Reset() { *x = DiskCacheEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[37] + mi := &file_bridge_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2739,7 +2895,7 @@ func (x *DiskCacheEvent) String() string { func (*DiskCacheEvent) ProtoMessage() {} func (x *DiskCacheEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[37] + mi := &file_bridge_proto_msgTypes[40] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2752,7 +2908,7 @@ func (x *DiskCacheEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use DiskCacheEvent.ProtoReflect.Descriptor instead. func (*DiskCacheEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{37} + return file_bridge_proto_rawDescGZIP(), []int{40} } func (m *DiskCacheEvent) GetEvent() isDiskCacheEvent_Event { @@ -2816,7 +2972,7 @@ type DiskCacheErrorEvent struct { func (x *DiskCacheErrorEvent) Reset() { *x = DiskCacheErrorEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[38] + mi := &file_bridge_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2829,7 +2985,7 @@ func (x *DiskCacheErrorEvent) String() string { func (*DiskCacheErrorEvent) ProtoMessage() {} func (x *DiskCacheErrorEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[38] + mi := &file_bridge_proto_msgTypes[41] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2842,7 +2998,7 @@ func (x *DiskCacheErrorEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use DiskCacheErrorEvent.ProtoReflect.Descriptor instead. func (*DiskCacheErrorEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{38} + return file_bridge_proto_rawDescGZIP(), []int{41} } func (x *DiskCacheErrorEvent) GetType() DiskCacheErrorType { @@ -2863,7 +3019,7 @@ type DiskCachePathChangedEvent struct { func (x *DiskCachePathChangedEvent) Reset() { *x = DiskCachePathChangedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[39] + mi := &file_bridge_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2876,7 +3032,7 @@ func (x *DiskCachePathChangedEvent) String() string { func (*DiskCachePathChangedEvent) ProtoMessage() {} func (x *DiskCachePathChangedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[39] + mi := &file_bridge_proto_msgTypes[42] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2889,7 +3045,7 @@ func (x *DiskCachePathChangedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use DiskCachePathChangedEvent.ProtoReflect.Descriptor instead. func (*DiskCachePathChangedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{39} + return file_bridge_proto_rawDescGZIP(), []int{42} } func (x *DiskCachePathChangedEvent) GetPath() string { @@ -2908,7 +3064,7 @@ type DiskCachePathChangeFinishedEvent struct { func (x *DiskCachePathChangeFinishedEvent) Reset() { *x = DiskCachePathChangeFinishedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[40] + mi := &file_bridge_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2921,7 +3077,7 @@ func (x *DiskCachePathChangeFinishedEvent) String() string { func (*DiskCachePathChangeFinishedEvent) ProtoMessage() {} func (x *DiskCachePathChangeFinishedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[40] + mi := &file_bridge_proto_msgTypes[43] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2934,7 +3090,7 @@ func (x *DiskCachePathChangeFinishedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use DiskCachePathChangeFinishedEvent.ProtoReflect.Descriptor instead. func (*DiskCachePathChangeFinishedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{40} + return file_bridge_proto_rawDescGZIP(), []int{43} } //********************************************************** @@ -2955,7 +3111,7 @@ type MailServerSettingsEvent struct { func (x *MailServerSettingsEvent) Reset() { *x = MailServerSettingsEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[41] + mi := &file_bridge_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2968,7 +3124,7 @@ func (x *MailServerSettingsEvent) String() string { func (*MailServerSettingsEvent) ProtoMessage() {} func (x *MailServerSettingsEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[41] + mi := &file_bridge_proto_msgTypes[44] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2981,7 +3137,7 @@ func (x *MailServerSettingsEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use MailServerSettingsEvent.ProtoReflect.Descriptor instead. func (*MailServerSettingsEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{41} + return file_bridge_proto_rawDescGZIP(), []int{44} } func (m *MailServerSettingsEvent) GetEvent() isMailServerSettingsEvent_Event { @@ -3045,7 +3201,7 @@ type MailServerSettingsErrorEvent struct { func (x *MailServerSettingsErrorEvent) Reset() { *x = MailServerSettingsErrorEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[42] + mi := &file_bridge_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3058,7 +3214,7 @@ func (x *MailServerSettingsErrorEvent) String() string { func (*MailServerSettingsErrorEvent) ProtoMessage() {} func (x *MailServerSettingsErrorEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[42] + mi := &file_bridge_proto_msgTypes[45] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3071,7 +3227,7 @@ func (x *MailServerSettingsErrorEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use MailServerSettingsErrorEvent.ProtoReflect.Descriptor instead. func (*MailServerSettingsErrorEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{42} + return file_bridge_proto_rawDescGZIP(), []int{45} } func (x *MailServerSettingsErrorEvent) GetType() MailServerSettingsErrorType { @@ -3092,7 +3248,7 @@ type MailServerSettingsChangedEvent struct { func (x *MailServerSettingsChangedEvent) Reset() { *x = MailServerSettingsChangedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[43] + mi := &file_bridge_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3105,7 +3261,7 @@ func (x *MailServerSettingsChangedEvent) String() string { func (*MailServerSettingsChangedEvent) ProtoMessage() {} func (x *MailServerSettingsChangedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[43] + mi := &file_bridge_proto_msgTypes[46] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3118,7 +3274,7 @@ func (x *MailServerSettingsChangedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use MailServerSettingsChangedEvent.ProtoReflect.Descriptor instead. func (*MailServerSettingsChangedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{43} + return file_bridge_proto_rawDescGZIP(), []int{46} } func (x *MailServerSettingsChangedEvent) GetSettings() *ImapSmtpSettings { @@ -3137,7 +3293,7 @@ type ChangeMailServerSettingsFinishedEvent struct { func (x *ChangeMailServerSettingsFinishedEvent) Reset() { *x = ChangeMailServerSettingsFinishedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[44] + mi := &file_bridge_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3150,7 +3306,7 @@ func (x *ChangeMailServerSettingsFinishedEvent) String() string { func (*ChangeMailServerSettingsFinishedEvent) ProtoMessage() {} func (x *ChangeMailServerSettingsFinishedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[44] + mi := &file_bridge_proto_msgTypes[47] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3163,7 +3319,7 @@ func (x *ChangeMailServerSettingsFinishedEvent) ProtoReflect() protoreflect.Mess // Deprecated: Use ChangeMailServerSettingsFinishedEvent.ProtoReflect.Descriptor instead. func (*ChangeMailServerSettingsFinishedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{44} + return file_bridge_proto_rawDescGZIP(), []int{47} } //********************************************************** @@ -3184,7 +3340,7 @@ type KeychainEvent struct { func (x *KeychainEvent) Reset() { *x = KeychainEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[45] + mi := &file_bridge_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3197,7 +3353,7 @@ func (x *KeychainEvent) String() string { func (*KeychainEvent) ProtoMessage() {} func (x *KeychainEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[45] + mi := &file_bridge_proto_msgTypes[48] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3210,7 +3366,7 @@ func (x *KeychainEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use KeychainEvent.ProtoReflect.Descriptor instead. func (*KeychainEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{45} + return file_bridge_proto_rawDescGZIP(), []int{48} } func (m *KeychainEvent) GetEvent() isKeychainEvent_Event { @@ -3272,7 +3428,7 @@ type ChangeKeychainFinishedEvent struct { func (x *ChangeKeychainFinishedEvent) Reset() { *x = ChangeKeychainFinishedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[46] + mi := &file_bridge_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3285,7 +3441,7 @@ func (x *ChangeKeychainFinishedEvent) String() string { func (*ChangeKeychainFinishedEvent) ProtoMessage() {} func (x *ChangeKeychainFinishedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[46] + mi := &file_bridge_proto_msgTypes[49] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3298,7 +3454,7 @@ func (x *ChangeKeychainFinishedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeKeychainFinishedEvent.ProtoReflect.Descriptor instead. func (*ChangeKeychainFinishedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{46} + return file_bridge_proto_rawDescGZIP(), []int{49} } type HasNoKeychainEvent struct { @@ -3310,7 +3466,7 @@ type HasNoKeychainEvent struct { func (x *HasNoKeychainEvent) Reset() { *x = HasNoKeychainEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[47] + mi := &file_bridge_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3323,7 +3479,7 @@ func (x *HasNoKeychainEvent) String() string { func (*HasNoKeychainEvent) ProtoMessage() {} func (x *HasNoKeychainEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[47] + mi := &file_bridge_proto_msgTypes[50] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3336,7 +3492,7 @@ func (x *HasNoKeychainEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use HasNoKeychainEvent.ProtoReflect.Descriptor instead. func (*HasNoKeychainEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{47} + return file_bridge_proto_rawDescGZIP(), []int{50} } type RebuildKeychainEvent struct { @@ -3348,7 +3504,7 @@ type RebuildKeychainEvent struct { func (x *RebuildKeychainEvent) Reset() { *x = RebuildKeychainEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[48] + mi := &file_bridge_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3361,7 +3517,7 @@ func (x *RebuildKeychainEvent) String() string { func (*RebuildKeychainEvent) ProtoMessage() {} func (x *RebuildKeychainEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[48] + mi := &file_bridge_proto_msgTypes[51] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3374,7 +3530,7 @@ func (x *RebuildKeychainEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use RebuildKeychainEvent.ProtoReflect.Descriptor instead. func (*RebuildKeychainEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{48} + return file_bridge_proto_rawDescGZIP(), []int{51} } //********************************************************** @@ -3396,7 +3552,7 @@ type MailEvent struct { func (x *MailEvent) Reset() { *x = MailEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[49] + mi := &file_bridge_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3409,7 +3565,7 @@ func (x *MailEvent) String() string { func (*MailEvent) ProtoMessage() {} func (x *MailEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[49] + mi := &file_bridge_proto_msgTypes[52] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3422,7 +3578,7 @@ func (x *MailEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use MailEvent.ProtoReflect.Descriptor instead. func (*MailEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{49} + return file_bridge_proto_rawDescGZIP(), []int{52} } func (m *MailEvent) GetEvent() isMailEvent_Event { @@ -3499,7 +3655,7 @@ type NoActiveKeyForRecipientEvent struct { func (x *NoActiveKeyForRecipientEvent) Reset() { *x = NoActiveKeyForRecipientEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[50] + mi := &file_bridge_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3512,7 +3668,7 @@ func (x *NoActiveKeyForRecipientEvent) String() string { func (*NoActiveKeyForRecipientEvent) ProtoMessage() {} func (x *NoActiveKeyForRecipientEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[50] + mi := &file_bridge_proto_msgTypes[53] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3525,7 +3681,7 @@ func (x *NoActiveKeyForRecipientEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use NoActiveKeyForRecipientEvent.ProtoReflect.Descriptor instead. func (*NoActiveKeyForRecipientEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{50} + return file_bridge_proto_rawDescGZIP(), []int{53} } func (x *NoActiveKeyForRecipientEvent) GetEmail() string { @@ -3546,7 +3702,7 @@ type AddressChangedEvent struct { func (x *AddressChangedEvent) Reset() { *x = AddressChangedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[51] + mi := &file_bridge_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3559,7 +3715,7 @@ func (x *AddressChangedEvent) String() string { func (*AddressChangedEvent) ProtoMessage() {} func (x *AddressChangedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[51] + mi := &file_bridge_proto_msgTypes[54] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3572,7 +3728,7 @@ func (x *AddressChangedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AddressChangedEvent.ProtoReflect.Descriptor instead. func (*AddressChangedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{51} + return file_bridge_proto_rawDescGZIP(), []int{54} } func (x *AddressChangedEvent) GetAddress() string { @@ -3593,7 +3749,7 @@ type AddressChangedLogoutEvent struct { func (x *AddressChangedLogoutEvent) Reset() { *x = AddressChangedLogoutEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[52] + mi := &file_bridge_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3606,7 +3762,7 @@ func (x *AddressChangedLogoutEvent) String() string { func (*AddressChangedLogoutEvent) ProtoMessage() {} func (x *AddressChangedLogoutEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[52] + mi := &file_bridge_proto_msgTypes[55] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3619,7 +3775,7 @@ func (x *AddressChangedLogoutEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AddressChangedLogoutEvent.ProtoReflect.Descriptor instead. func (*AddressChangedLogoutEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{52} + return file_bridge_proto_rawDescGZIP(), []int{55} } func (x *AddressChangedLogoutEvent) GetAddress() string { @@ -3638,7 +3794,7 @@ type ApiCertIssueEvent struct { func (x *ApiCertIssueEvent) Reset() { *x = ApiCertIssueEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[53] + mi := &file_bridge_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3651,7 +3807,7 @@ func (x *ApiCertIssueEvent) String() string { func (*ApiCertIssueEvent) ProtoMessage() {} func (x *ApiCertIssueEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[53] + mi := &file_bridge_proto_msgTypes[56] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3664,7 +3820,7 @@ func (x *ApiCertIssueEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ApiCertIssueEvent.ProtoReflect.Descriptor instead. func (*ApiCertIssueEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{53} + return file_bridge_proto_rawDescGZIP(), []int{56} } type UserEvent struct { @@ -3688,7 +3844,7 @@ type UserEvent struct { func (x *UserEvent) Reset() { *x = UserEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[54] + mi := &file_bridge_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3701,7 +3857,7 @@ func (x *UserEvent) String() string { func (*UserEvent) ProtoMessage() {} func (x *UserEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[54] + mi := &file_bridge_proto_msgTypes[57] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3714,7 +3870,7 @@ func (x *UserEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UserEvent.ProtoReflect.Descriptor instead. func (*UserEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{54} + return file_bridge_proto_rawDescGZIP(), []int{57} } func (m *UserEvent) GetEvent() isUserEvent_Event { @@ -3856,7 +4012,7 @@ type ToggleSplitModeFinishedEvent struct { func (x *ToggleSplitModeFinishedEvent) Reset() { *x = ToggleSplitModeFinishedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[55] + mi := &file_bridge_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3869,7 +4025,7 @@ func (x *ToggleSplitModeFinishedEvent) String() string { func (*ToggleSplitModeFinishedEvent) ProtoMessage() {} func (x *ToggleSplitModeFinishedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[55] + mi := &file_bridge_proto_msgTypes[58] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3882,7 +4038,7 @@ func (x *ToggleSplitModeFinishedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ToggleSplitModeFinishedEvent.ProtoReflect.Descriptor instead. func (*ToggleSplitModeFinishedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{55} + return file_bridge_proto_rawDescGZIP(), []int{58} } func (x *ToggleSplitModeFinishedEvent) GetUserID() string { @@ -3903,7 +4059,7 @@ type UserDisconnectedEvent struct { func (x *UserDisconnectedEvent) Reset() { *x = UserDisconnectedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[56] + mi := &file_bridge_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3916,7 +4072,7 @@ func (x *UserDisconnectedEvent) String() string { func (*UserDisconnectedEvent) ProtoMessage() {} func (x *UserDisconnectedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[56] + mi := &file_bridge_proto_msgTypes[59] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3929,7 +4085,7 @@ func (x *UserDisconnectedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UserDisconnectedEvent.ProtoReflect.Descriptor instead. func (*UserDisconnectedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{56} + return file_bridge_proto_rawDescGZIP(), []int{59} } func (x *UserDisconnectedEvent) GetUsername() string { @@ -3950,7 +4106,7 @@ type UserChangedEvent struct { func (x *UserChangedEvent) Reset() { *x = UserChangedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[57] + mi := &file_bridge_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3963,7 +4119,7 @@ func (x *UserChangedEvent) String() string { func (*UserChangedEvent) ProtoMessage() {} func (x *UserChangedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[57] + mi := &file_bridge_proto_msgTypes[60] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3976,7 +4132,7 @@ func (x *UserChangedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UserChangedEvent.ProtoReflect.Descriptor instead. func (*UserChangedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{57} + return file_bridge_proto_rawDescGZIP(), []int{60} } func (x *UserChangedEvent) GetUserID() string { @@ -3998,7 +4154,7 @@ type UserBadEvent struct { func (x *UserBadEvent) Reset() { *x = UserBadEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[58] + mi := &file_bridge_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4011,7 +4167,7 @@ func (x *UserBadEvent) String() string { func (*UserBadEvent) ProtoMessage() {} func (x *UserBadEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[58] + mi := &file_bridge_proto_msgTypes[61] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4024,7 +4180,7 @@ func (x *UserBadEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UserBadEvent.ProtoReflect.Descriptor instead. func (*UserBadEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{58} + return file_bridge_proto_rawDescGZIP(), []int{61} } func (x *UserBadEvent) GetUserID() string { @@ -4053,7 +4209,7 @@ type UsedBytesChangedEvent struct { func (x *UsedBytesChangedEvent) Reset() { *x = UsedBytesChangedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[59] + mi := &file_bridge_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4066,7 +4222,7 @@ func (x *UsedBytesChangedEvent) String() string { func (*UsedBytesChangedEvent) ProtoMessage() {} func (x *UsedBytesChangedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[59] + mi := &file_bridge_proto_msgTypes[62] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4079,7 +4235,7 @@ func (x *UsedBytesChangedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UsedBytesChangedEvent.ProtoReflect.Descriptor instead. func (*UsedBytesChangedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{59} + return file_bridge_proto_rawDescGZIP(), []int{62} } func (x *UsedBytesChangedEvent) GetUserID() string { @@ -4107,7 +4263,7 @@ type ImapLoginFailedEvent struct { func (x *ImapLoginFailedEvent) Reset() { *x = ImapLoginFailedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[60] + mi := &file_bridge_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4120,7 +4276,7 @@ func (x *ImapLoginFailedEvent) String() string { func (*ImapLoginFailedEvent) ProtoMessage() {} func (x *ImapLoginFailedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[60] + mi := &file_bridge_proto_msgTypes[63] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4133,7 +4289,7 @@ func (x *ImapLoginFailedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ImapLoginFailedEvent.ProtoReflect.Descriptor instead. func (*ImapLoginFailedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{60} + return file_bridge_proto_rawDescGZIP(), []int{63} } func (x *ImapLoginFailedEvent) GetUsername() string { @@ -4154,7 +4310,7 @@ type SyncStartedEvent struct { func (x *SyncStartedEvent) Reset() { *x = SyncStartedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[61] + mi := &file_bridge_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4167,7 +4323,7 @@ func (x *SyncStartedEvent) String() string { func (*SyncStartedEvent) ProtoMessage() {} func (x *SyncStartedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[61] + mi := &file_bridge_proto_msgTypes[64] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4180,7 +4336,7 @@ func (x *SyncStartedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SyncStartedEvent.ProtoReflect.Descriptor instead. func (*SyncStartedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{61} + return file_bridge_proto_rawDescGZIP(), []int{64} } func (x *SyncStartedEvent) GetUserID() string { @@ -4201,7 +4357,7 @@ type SyncFinishedEvent struct { func (x *SyncFinishedEvent) Reset() { *x = SyncFinishedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[62] + mi := &file_bridge_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4214,7 +4370,7 @@ func (x *SyncFinishedEvent) String() string { func (*SyncFinishedEvent) ProtoMessage() {} func (x *SyncFinishedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[62] + mi := &file_bridge_proto_msgTypes[65] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4227,7 +4383,7 @@ func (x *SyncFinishedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SyncFinishedEvent.ProtoReflect.Descriptor instead. func (*SyncFinishedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{62} + return file_bridge_proto_rawDescGZIP(), []int{65} } func (x *SyncFinishedEvent) GetUserID() string { @@ -4251,7 +4407,7 @@ type SyncProgressEvent struct { func (x *SyncProgressEvent) Reset() { *x = SyncProgressEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[63] + mi := &file_bridge_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4264,7 +4420,7 @@ func (x *SyncProgressEvent) String() string { func (*SyncProgressEvent) ProtoMessage() {} func (x *SyncProgressEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[63] + mi := &file_bridge_proto_msgTypes[66] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4277,7 +4433,7 @@ func (x *SyncProgressEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SyncProgressEvent.ProtoReflect.Descriptor instead. func (*SyncProgressEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{63} + return file_bridge_proto_rawDescGZIP(), []int{66} } func (x *SyncProgressEvent) GetUserID() string { @@ -4319,7 +4475,7 @@ type GenericErrorEvent struct { func (x *GenericErrorEvent) Reset() { *x = GenericErrorEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[64] + mi := &file_bridge_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4332,7 +4488,7 @@ func (x *GenericErrorEvent) String() string { func (*GenericErrorEvent) ProtoMessage() {} func (x *GenericErrorEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[64] + mi := &file_bridge_proto_msgTypes[67] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4345,7 +4501,7 @@ func (x *GenericErrorEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use GenericErrorEvent.ProtoReflect.Descriptor instead. func (*GenericErrorEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{64} + return file_bridge_proto_rawDescGZIP(), []int{67} } func (x *GenericErrorEvent) GetCode() ErrorCode { @@ -4477,7 +4633,7 @@ var file_bridge_proto_rawDesc = []byte{ 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, - 0x65, 0x6e, 0x74, 0x22, 0xeb, 0x04, 0x0a, 0x08, 0x41, 0x70, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x22, 0x9d, 0x07, 0x0a, 0x08, 0x41, 0x70, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x43, 0x0a, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x76, @@ -4515,630 +4671,665 @@ var file_bridge_proto_rawDesc = []byte{ 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, - 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, - 0x74, 0x22, 0x33, 0x0a, 0x13, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x1e, 0x0a, 0x1c, 0x54, 0x6f, 0x67, 0x67, 0x6c, 0x65, - 0x41, 0x75, 0x74, 0x6f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x14, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x65, 0x74, 0x46, - 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x18, 0x0a, 0x16, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x17, 0x0a, 0x15, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x42, 0x75, 0x67, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, - 0x15, 0x0a, 0x13, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x45, 0x72, 0x72, 0x6f, - 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x15, 0x0a, 0x13, 0x53, 0x68, 0x6f, 0x77, 0x4d, 0x61, - 0x69, 0x6e, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x18, 0x0a, - 0x16, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, - 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xe3, 0x02, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x69, - 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2d, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, - 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x42, 0x0a, 0x0c, 0x74, 0x66, 0x61, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x72, - 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x66, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x74, 0x66, 0x61, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x12, 0x5b, 0x0a, 0x14, 0x74, 0x77, 0x6f, - 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, - 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x77, 0x6f, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, - 0x52, 0x14, 0x74, 0x77, 0x6f, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x12, 0x36, 0x0a, 0x08, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, - 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, - 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x48, 0x00, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x44, - 0x0a, 0x0f, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x64, 0x49, - 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, - 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x48, 0x00, 0x52, 0x0f, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x4c, 0x6f, 0x67, 0x67, - 0x65, 0x64, 0x49, 0x6e, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x55, 0x0a, - 0x0f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x12, 0x28, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x22, 0x34, 0x0a, 0x16, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x66, 0x61, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, - 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x21, 0x0a, 0x1f, 0x4c, 0x6f, - 0x67, 0x69, 0x6e, 0x54, 0x77, 0x6f, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x50, 0x0a, - 0x12, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x77, - 0x61, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0c, 0x77, 0x61, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4f, 0x75, 0x74, 0x22, - 0xb9, 0x04, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, - 0x2e, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, - 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, - 0x40, 0x0a, 0x0b, 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x61, 0x64, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x61, 0x64, 0x79, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x61, 0x64, - 0x79, 0x12, 0x58, 0x0a, 0x13, 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x75, - 0x61, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x13, 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x05, 0x66, - 0x6f, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, - 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x53, 0x0a, 0x13, 0x73, - 0x69, 0x6c, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, - 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x48, 0x00, 0x52, 0x13, 0x73, 0x69, 0x6c, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, - 0x12, 0x47, 0x0a, 0x0f, 0x69, 0x73, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x72, 0x70, 0x63, - 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x73, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x0f, 0x69, 0x73, 0x4c, 0x61, 0x74, 0x65, - 0x73, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x0d, 0x63, 0x68, 0x65, - 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, - 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0d, 0x63, - 0x68, 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x44, 0x0a, 0x0e, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, - 0x48, 0x00, 0x52, 0x0e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x3d, 0x0a, 0x10, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, - 0x29, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, - 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x32, 0x0a, 0x16, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x61, 0x64, 0x79, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x20, - 0x0a, 0x1e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x22, 0x2c, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x1b, - 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x22, 0x17, 0x0a, 0x15, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x73, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x15, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, - 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x22, 0x16, 0x0a, 0x14, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x64, 0x22, 0xeb, 0x01, 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, - 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x31, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, + 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x64, 0x0a, 0x19, 0x63, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x53, 0x75, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, + 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x19, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, + 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x67, + 0x0a, 0x1a, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, + 0x74, 0x61, 0x6c, 0x6c, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x43, 0x61, 0x6e, 0x63, + 0x65, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x1a, 0x63, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x43, + 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x12, 0x61, 0x0a, 0x18, 0x63, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x46, 0x61, 0x69, + 0x6c, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, + 0x61, 0x6c, 0x6c, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x18, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, + 0x74, 0x61, 0x6c, 0x6c, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, + 0x65, 0x6e, 0x74, 0x22, 0x33, 0x0a, 0x13, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x1e, 0x0a, 0x1c, 0x54, 0x6f, 0x67, 0x67, + 0x6c, 0x65, 0x41, 0x75, 0x74, 0x6f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x46, 0x69, 0x6e, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x14, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x65, + 0x74, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x18, + 0x0a, 0x16, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x46, 0x69, 0x6e, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x17, 0x0a, 0x15, 0x52, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x42, 0x75, 0x67, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x22, 0x15, 0x0a, 0x13, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x15, 0x0a, 0x13, 0x53, 0x68, 0x6f, 0x77, + 0x4d, 0x61, 0x69, 0x6e, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, + 0x18, 0x0a, 0x16, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x46, 0x61, 0x6c, 0x6c, + 0x62, 0x61, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x20, 0x0a, 0x1e, 0x43, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x53, + 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x21, 0x0a, 0x1f, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, + 0x6c, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x1f, + 0x0a, 0x1d, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, + 0x74, 0x61, 0x6c, 0x6c, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, + 0xe3, 0x02, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2d, + 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x42, 0x0a, + 0x0c, 0x74, 0x66, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, + 0x54, 0x66, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x0c, 0x74, 0x66, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, + 0x64, 0x12, 0x5b, 0x0a, 0x14, 0x74, 0x77, 0x6f, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x25, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x77, 0x6f, 0x50, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, + 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x14, 0x74, 0x77, 0x6f, 0x50, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x12, 0x36, + 0x0a, 0x08, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x69, 0x6e, + 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x08, 0x66, 0x69, + 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x44, 0x0a, 0x0f, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, + 0x79, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x64, 0x49, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, + 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x61, 0x6c, 0x72, + 0x65, 0x61, 0x64, 0x79, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x64, 0x49, 0x6e, 0x42, 0x07, 0x0a, 0x05, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x55, 0x0a, 0x0f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, + 0x67, 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x34, 0x0a, 0x16, + 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x66, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, + 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, + 0x6d, 0x65, 0x22, 0x21, 0x0a, 0x1f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x77, 0x6f, 0x50, 0x61, + 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x50, 0x0a, 0x12, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x69, + 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, + 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x77, 0x61, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, + 0x4f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x77, 0x61, 0x73, 0x53, 0x69, + 0x67, 0x6e, 0x65, 0x64, 0x4f, 0x75, 0x74, 0x22, 0xb9, 0x04, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x40, 0x0a, 0x0b, 0x6d, 0x61, 0x6e, 0x75, 0x61, + 0x6c, 0x52, 0x65, 0x61, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, + 0x52, 0x65, 0x61, 0x64, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x6d, 0x61, + 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x61, 0x64, 0x79, 0x12, 0x58, 0x0a, 0x13, 0x6d, 0x61, 0x6e, + 0x75, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x13, + 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, + 0x64, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x46, 0x6f, 0x72, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x66, 0x6f, + 0x72, 0x63, 0x65, 0x12, 0x53, 0x0a, 0x13, 0x73, 0x69, 0x6c, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, + 0x6c, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, + 0x64, 0x48, 0x00, 0x52, 0x13, 0x73, 0x69, 0x6c, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x12, 0x47, 0x0a, 0x0f, 0x69, 0x73, 0x4c, 0x61, + 0x74, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, + 0x73, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x00, + 0x52, 0x0f, 0x69, 0x73, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x41, 0x0a, 0x0d, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, + 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0d, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, + 0x73, 0x68, 0x65, 0x64, 0x12, 0x44, 0x0a, 0x0e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, + 0x65, 0x6e, 0x74, 0x22, 0x3d, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x22, 0x32, 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x75, + 0x61, 0x6c, 0x52, 0x65, 0x61, 0x64, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x20, 0x0a, 0x1e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, + 0x64, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x2c, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x1b, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, + 0x64, 0x65, 0x64, 0x22, 0x17, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x73, 0x4c, + 0x61, 0x74, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x15, 0x0a, 0x13, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x22, 0x16, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x22, 0xeb, 0x01, 0x0a, 0x0e, + 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x31, + 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x12, 0x43, 0x0a, 0x0b, 0x70, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, + 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x70, 0x61, 0x74, 0x68, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x58, 0x0a, 0x12, 0x70, 0x61, 0x74, 0x68, 0x43, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, + 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6e, + 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x70, 0x61, + 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, + 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x43, 0x0a, 0x13, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x43, 0x0a, 0x0b, 0x70, 0x61, 0x74, - 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, - 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, - 0x00, 0x52, 0x0b, 0x70, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x58, - 0x0a, 0x12, 0x70, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6e, 0x69, - 0x73, 0x68, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x67, 0x72, 0x70, - 0x63, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x43, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x70, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, - 0x74, 0x22, 0x43, 0x0a, 0x13, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, - 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, - 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2f, 0x0a, 0x19, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, - 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x22, 0x0a, 0x20, 0x44, 0x69, 0x73, 0x6b, 0x43, - 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, - 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xbf, 0x02, 0x0a, 0x17, - 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, - 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, - 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x12, 0x64, 0x0a, 0x19, 0x6d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, - 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, - 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x19, - 0x6d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x79, 0x0a, 0x20, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, - 0x74, 0x69, 0x6e, 0x67, 0x73, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, - 0x6e, 0x67, 0x73, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x48, 0x00, 0x52, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x46, 0x69, 0x6e, 0x69, - 0x73, 0x68, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x55, 0x0a, - 0x1c, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, - 0x6e, 0x67, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x35, 0x0a, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x67, 0x72, - 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, - 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, - 0x74, 0x79, 0x70, 0x65, 0x22, 0x54, 0x0a, 0x1e, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, + 0x12, 0x2c, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2f, + 0x0a, 0x19, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, + 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, + 0x22, 0x0a, 0x20, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, + 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x22, 0xbf, 0x02, 0x0a, 0x17, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, + 0x3a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x64, 0x0a, 0x19, 0x6d, + 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, + 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x19, 0x6d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, - 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, - 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x27, 0x0a, 0x25, 0x43, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, - 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x22, 0xff, 0x01, 0x0a, 0x0d, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x5b, 0x0a, 0x16, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4b, - 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, - 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x16, 0x63, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, - 0x65, 0x64, 0x12, 0x40, 0x0a, 0x0d, 0x68, 0x61, 0x73, 0x4e, 0x6f, 0x4b, 0x65, 0x79, 0x63, 0x68, - 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, - 0x2e, 0x48, 0x61, 0x73, 0x4e, 0x6f, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x68, 0x61, 0x73, 0x4e, 0x6f, 0x4b, 0x65, 0x79, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x12, 0x46, 0x0a, 0x0f, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4b, - 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x72, 0x65, 0x62, - 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x07, 0x0a, 0x05, - 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x1d, 0x0a, 0x1b, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4b, - 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x22, 0x14, 0x0a, 0x12, 0x48, 0x61, 0x73, 0x4e, 0x6f, 0x4b, 0x65, 0x79, - 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x16, 0x0a, 0x14, 0x52, 0x65, - 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x22, 0xd9, 0x02, 0x0a, 0x09, 0x4d, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x12, 0x68, 0x0a, 0x1c, 0x6e, 0x6f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x46, - 0x6f, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, - 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x46, 0x6f, 0x72, 0x52, 0x65, 0x63, 0x69, - 0x70, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x1c, 0x6e, 0x6f, - 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x46, 0x6f, 0x72, 0x52, 0x65, 0x63, 0x69, - 0x70, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x43, 0x0a, 0x0e, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, - 0x0e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, - 0x55, 0x0a, 0x14, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x64, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, - 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x64, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, - 0x52, 0x14, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, - 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x3d, 0x0a, 0x0c, 0x61, 0x70, 0x69, 0x43, 0x65, 0x72, - 0x74, 0x49, 0x73, 0x73, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, - 0x72, 0x70, 0x63, 0x2e, 0x41, 0x70, 0x69, 0x43, 0x65, 0x72, 0x74, 0x49, 0x73, 0x73, 0x75, 0x65, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x61, 0x70, 0x69, 0x43, 0x65, 0x72, 0x74, - 0x49, 0x73, 0x73, 0x75, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x34, - 0x0a, 0x1c, 0x4e, 0x6f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x46, 0x6f, 0x72, - 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x14, - 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, - 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x2f, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x35, 0x0a, 0x19, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x13, 0x0a, 0x11, - 0x41, 0x70, 0x69, 0x43, 0x65, 0x72, 0x74, 0x49, 0x73, 0x73, 0x75, 0x65, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x22, 0xb4, 0x05, 0x0a, 0x09, 0x55, 0x73, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, - 0x5e, 0x0a, 0x17, 0x74, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, - 0x64, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x22, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x53, 0x70, - 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x17, 0x74, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x53, 0x70, - 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, - 0x49, 0x0a, 0x10, 0x75, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x72, 0x70, 0x63, - 0x2e, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x10, 0x75, 0x73, 0x65, 0x72, 0x44, 0x69, - 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x3a, 0x0a, 0x0b, 0x75, 0x73, - 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x43, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x42, 0x61, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, - 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x48, 0x00, 0x52, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x12, 0x53, 0x0a, 0x15, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x43, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1b, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, - 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x15, - 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x50, 0x0a, 0x14, 0x69, 0x6d, 0x61, 0x70, 0x4c, 0x6f, 0x67, - 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x4c, - 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, - 0x00, 0x52, 0x14, 0x69, 0x6d, 0x61, 0x70, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, - 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x44, 0x0a, 0x10, 0x73, 0x79, 0x6e, 0x63, 0x53, - 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, - 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x10, 0x73, 0x79, 0x6e, - 0x63, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, - 0x11, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, - 0x53, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x48, 0x00, 0x52, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x50, 0x72, - 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x50, 0x72, 0x6f, - 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, 0x73, 0x79, - 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, - 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x36, 0x0a, 0x1c, 0x54, 0x6f, 0x67, 0x67, - 0x6c, 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, - 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, - 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, - 0x22, 0x33, 0x0a, 0x15, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, - 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, - 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x2a, 0x0a, 0x10, 0x55, 0x73, 0x65, 0x72, 0x43, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, + 0x64, 0x12, 0x79, 0x0a, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x46, 0x69, 0x6e, + 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x46, 0x69, 0x6e, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x20, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x73, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x55, 0x0a, 0x1c, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x35, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x69, 0x6c, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x54, 0x0a, 0x1e, + 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, + 0x67, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x32, + 0x0a, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, + 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, + 0x67, 0x73, 0x22, 0x27, 0x0a, 0x25, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x46, 0x69, + 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xff, 0x01, 0x0a, 0x0d, + 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x5b, 0x0a, + 0x16, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x46, + 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x48, 0x00, 0x52, 0x16, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, + 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x40, 0x0a, 0x0d, 0x68, 0x61, + 0x73, 0x4e, 0x6f, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x61, 0x73, 0x4e, 0x6f, 0x4b, 0x65, + 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x68, + 0x61, 0x73, 0x4e, 0x6f, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x46, 0x0a, 0x0f, + 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x62, + 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x0f, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x63, + 0x68, 0x61, 0x69, 0x6e, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x1d, 0x0a, + 0x1b, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x46, + 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x14, 0x0a, 0x12, + 0x48, 0x61, 0x73, 0x4e, 0x6f, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x22, 0x16, 0x0a, 0x14, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xd9, 0x02, 0x0a, 0x09, 0x4d, + 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x68, 0x0a, 0x1c, 0x6e, 0x6f, 0x41, 0x63, + 0x74, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x46, 0x6f, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, + 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4b, 0x65, + 0x79, 0x46, 0x6f, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x1c, 0x6e, 0x6f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4b, 0x65, + 0x79, 0x46, 0x6f, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x12, 0x43, 0x0a, 0x0e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x55, 0x0a, 0x14, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x4c, 0x6f, 0x67, 0x6f, 0x75, + 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x14, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x3d, + 0x0a, 0x0c, 0x61, 0x70, 0x69, 0x43, 0x65, 0x72, 0x74, 0x49, 0x73, 0x73, 0x75, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x70, 0x69, 0x43, + 0x65, 0x72, 0x74, 0x49, 0x73, 0x73, 0x75, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, + 0x0c, 0x61, 0x70, 0x69, 0x43, 0x65, 0x72, 0x74, 0x49, 0x73, 0x73, 0x75, 0x65, 0x42, 0x07, 0x0a, + 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x34, 0x0a, 0x1c, 0x4e, 0x6f, 0x41, 0x63, 0x74, 0x69, + 0x76, 0x65, 0x4b, 0x65, 0x79, 0x46, 0x6f, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, + 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x2f, 0x0a, 0x13, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x35, 0x0a, + 0x19, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x4c, + 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x41, 0x70, 0x69, 0x43, 0x65, 0x72, 0x74, 0x49, + 0x73, 0x73, 0x75, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xb4, 0x05, 0x0a, 0x09, 0x55, 0x73, + 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x5e, 0x0a, 0x17, 0x74, 0x6f, 0x67, 0x67, 0x6c, + 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, + 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, + 0x54, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, + 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x17, + 0x74, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, + 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x49, 0x0a, 0x10, 0x75, 0x73, 0x65, 0x72, 0x44, + 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x10, 0x75, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x65, 0x64, 0x12, 0x3a, 0x0a, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, + 0x73, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, + 0x00, 0x52, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x38, + 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, + 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x75, 0x73, 0x65, 0x72, + 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x53, 0x0a, 0x15, 0x75, 0x73, 0x65, 0x64, + 0x42, 0x79, 0x74, 0x65, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, + 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x15, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, + 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x50, 0x0a, + 0x14, 0x69, 0x6d, 0x61, 0x70, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, + 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x14, 0x69, 0x6d, 0x61, 0x70, 0x4c, + 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, + 0x44, 0x0a, 0x10, 0x73, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x10, 0x73, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, + 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, + 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, 0x73, 0x79, 0x6e, + 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x47, + 0x0a, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, + 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x22, 0x36, 0x0a, 0x1c, 0x54, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, + 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x22, 0x33, 0x0a, 0x15, 0x55, 0x73, 0x65, 0x72, + 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x2a, 0x0a, + 0x10, 0x55, 0x73, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x22, 0x4a, 0x0a, 0x0c, 0x55, 0x73, 0x65, + 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, - 0x44, 0x22, 0x4a, 0x0a, 0x0c, 0x55, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x4d, 0x0a, - 0x15, 0x55, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x1c, - 0x0a, 0x09, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x09, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0x32, 0x0a, 0x14, - 0x49, 0x6d, 0x61, 0x70, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, - 0x22, 0x2a, 0x0a, 0x10, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x22, 0x2b, 0x0a, 0x11, - 0x53, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x22, 0x87, 0x01, 0x0a, 0x11, 0x53, 0x79, - 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, - 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, - 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, - 0x65, 0x73, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x4d, 0x73, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x65, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x4d, - 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x4d, 0x73, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, - 0x67, 0x4d, 0x73, 0x22, 0x38, 0x0a, 0x11, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x2a, 0x71, 0x0a, - 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, - 0x5f, 0x50, 0x41, 0x4e, 0x49, 0x43, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, - 0x46, 0x41, 0x54, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x45, - 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x4f, 0x47, 0x5f, 0x57, 0x41, - 0x52, 0x4e, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x4f, 0x47, 0x5f, 0x49, 0x4e, 0x46, 0x4f, - 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, - 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x06, - 0x2a, 0x36, 0x0a, 0x09, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, - 0x0a, 0x53, 0x49, 0x47, 0x4e, 0x45, 0x44, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x00, 0x12, 0x0a, 0x0a, - 0x06, 0x4c, 0x4f, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4e, - 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, 0x2a, 0xa2, 0x01, 0x0a, 0x0e, 0x4c, 0x6f, 0x67, - 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x55, - 0x53, 0x45, 0x52, 0x4e, 0x41, 0x4d, 0x45, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, - 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x52, 0x45, 0x45, - 0x5f, 0x55, 0x53, 0x45, 0x52, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x4f, 0x4e, 0x4e, 0x45, - 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x0d, 0x0a, - 0x09, 0x54, 0x46, 0x41, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, - 0x54, 0x46, 0x41, 0x5f, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x10, 0x04, 0x12, 0x17, 0x0a, 0x13, 0x54, - 0x57, 0x4f, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x53, 0x5f, 0x45, 0x52, 0x52, - 0x4f, 0x52, 0x10, 0x05, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x57, 0x4f, 0x5f, 0x50, 0x41, 0x53, 0x53, - 0x57, 0x4f, 0x52, 0x44, 0x53, 0x5f, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x10, 0x06, 0x2a, 0x5b, 0x0a, - 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x4d, 0x41, 0x4e, 0x55, 0x41, - 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x55, 0x50, 0x44, - 0x41, 0x54, 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, - 0x01, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x53, 0x49, 0x4c, 0x45, - 0x4e, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x2a, 0x6b, 0x0a, 0x12, 0x44, 0x69, - 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x20, 0x0a, 0x1c, 0x44, 0x49, 0x53, 0x4b, 0x5f, 0x43, 0x41, 0x43, 0x48, 0x45, 0x5f, 0x55, - 0x4e, 0x41, 0x56, 0x41, 0x49, 0x4c, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, - 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x41, 0x4e, 0x54, 0x5f, 0x4d, 0x4f, 0x56, 0x45, 0x5f, - 0x44, 0x49, 0x53, 0x4b, 0x5f, 0x43, 0x41, 0x43, 0x48, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, - 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x44, 0x49, 0x53, 0x4b, 0x5f, 0x46, 0x55, 0x4c, 0x4c, 0x5f, - 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x2a, 0xdd, 0x01, 0x0a, 0x1b, 0x4d, 0x61, 0x69, 0x6c, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x49, 0x4d, 0x41, 0x50, 0x5f, - 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x55, 0x50, 0x5f, 0x45, 0x52, 0x52, - 0x4f, 0x52, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x4d, 0x54, 0x50, 0x5f, 0x50, 0x4f, 0x52, - 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x55, 0x50, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, - 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x49, 0x4d, 0x41, 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x43, - 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x1a, 0x0a, - 0x16, 0x53, 0x4d, 0x54, 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, - 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x25, 0x0a, 0x21, 0x49, 0x4d, 0x41, - 0x50, 0x5f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x4f, 0x44, - 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, - 0x12, 0x25, 0x0a, 0x21, 0x53, 0x4d, 0x54, 0x50, 0x5f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, - 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, - 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x2a, 0x53, 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x43, 0x6f, 0x64, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, - 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x54, 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, 0xe2, 0x20, 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, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x4c, 0x6f, 0x67, 0x45, - 0x6e, 0x74, 0x72, 0x79, 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, 0x3a, 0x0a, 0x08, 0x47, 0x75, 0x69, 0x52, 0x65, 0x61, 0x64, 0x79, 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, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x75, 0x69, 0x52, 0x65, 0x61, 0x64, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x36, 0x0a, 0x04, 0x51, 0x75, 0x69, 0x74, 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, 0x39, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x61, - 0x72, 0x74, 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, 0x43, 0x0a, 0x0d, 0x53, 0x68, 0x6f, 0x77, 0x4f, 0x6e, 0x53, 0x74, 0x61, 0x72, - 0x74, 0x75, 0x70, 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, 0x46, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x49, 0x73, - 0x41, 0x75, 0x74, 0x6f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4f, 0x6e, 0x12, 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, 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, - 0x43, 0x0a, 0x0d, 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4f, 0x6e, - 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, 0x46, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x49, 0x73, 0x42, 0x65, 0x74, - 0x61, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 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, 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, 0x43, 0x0a, 0x0d, - 0x49, 0x73, 0x42, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x61, 0x62, 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, 0x49, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x49, 0x73, 0x41, 0x6c, 0x6c, 0x4d, 0x61, 0x69, - 0x6c, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x12, 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, 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, 0x46, 0x0a, 0x10, - 0x49, 0x73, 0x41, 0x6c, 0x6c, 0x4d, 0x61, 0x69, 0x6c, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 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, 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, 0x4c, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x49, 0x73, 0x54, 0x65, 0x6c, - 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1a, + 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x4d, 0x0a, 0x15, 0x55, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, + 0x65, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, + 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, + 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x73, 0x65, 0x64, 0x42, + 0x79, 0x74, 0x65, 0x73, 0x22, 0x32, 0x0a, 0x14, 0x49, 0x6d, 0x61, 0x70, 0x4c, 0x6f, 0x67, 0x69, + 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x2a, 0x0a, 0x10, 0x53, 0x79, 0x6e, 0x63, + 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x44, 0x22, 0x2b, 0x0a, 0x11, 0x53, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, + 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x44, 0x22, 0x87, 0x01, 0x0a, 0x11, 0x53, 0x79, 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, + 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, + 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x01, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x65, + 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x4d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, + 0x65, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x4d, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x72, 0x65, 0x6d, + 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x4d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x4d, 0x73, 0x22, 0x38, 0x0a, 0x11, 0x47, + 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x12, 0x23, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x52, + 0x04, 0x63, 0x6f, 0x64, 0x65, 0x2a, 0x71, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, + 0x6c, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x50, 0x41, 0x4e, 0x49, 0x43, 0x10, 0x00, + 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x10, 0x01, 0x12, + 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x0c, + 0x0a, 0x08, 0x4c, 0x4f, 0x47, 0x5f, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, + 0x4c, 0x4f, 0x47, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, + 0x47, 0x5f, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, + 0x5f, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x06, 0x2a, 0x36, 0x0a, 0x09, 0x55, 0x73, 0x65, 0x72, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x49, 0x47, 0x4e, 0x45, 0x44, 0x5f, + 0x4f, 0x55, 0x54, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x4f, 0x43, 0x4b, 0x45, 0x44, 0x10, + 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, + 0x2a, 0xa2, 0x01, 0x0a, 0x0e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x55, 0x53, 0x45, 0x52, 0x4e, 0x41, 0x4d, 0x45, 0x5f, + 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, + 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x52, 0x45, 0x45, 0x5f, 0x55, 0x53, 0x45, 0x52, 0x10, 0x01, 0x12, + 0x14, 0x0a, 0x10, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x52, + 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x46, 0x41, 0x5f, 0x45, 0x52, 0x52, + 0x4f, 0x52, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x46, 0x41, 0x5f, 0x41, 0x42, 0x4f, 0x52, + 0x54, 0x10, 0x04, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x57, 0x4f, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x57, + 0x4f, 0x52, 0x44, 0x53, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x12, 0x17, 0x0a, 0x13, + 0x54, 0x57, 0x4f, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x53, 0x5f, 0x41, 0x42, + 0x4f, 0x52, 0x54, 0x10, 0x06, 0x2a, 0x5b, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x44, 0x41, + 0x54, 0x45, 0x5f, 0x4d, 0x41, 0x4e, 0x55, 0x41, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, + 0x00, 0x12, 0x16, 0x0a, 0x12, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x43, + 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x44, + 0x41, 0x54, 0x45, 0x5f, 0x53, 0x49, 0x4c, 0x45, 0x4e, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, + 0x10, 0x02, 0x2a, 0x6b, 0x0a, 0x12, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x1c, 0x44, 0x49, 0x53, 0x4b, + 0x5f, 0x43, 0x41, 0x43, 0x48, 0x45, 0x5f, 0x55, 0x4e, 0x41, 0x56, 0x41, 0x49, 0x4c, 0x41, 0x42, + 0x4c, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x41, + 0x4e, 0x54, 0x5f, 0x4d, 0x4f, 0x56, 0x45, 0x5f, 0x44, 0x49, 0x53, 0x4b, 0x5f, 0x43, 0x41, 0x43, + 0x48, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x44, 0x49, + 0x53, 0x4b, 0x5f, 0x46, 0x55, 0x4c, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x2a, + 0xdd, 0x01, 0x0a, 0x1b, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, + 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x1b, 0x0a, 0x17, 0x49, 0x4d, 0x41, 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x53, 0x54, 0x41, + 0x52, 0x54, 0x55, 0x50, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, + 0x53, 0x4d, 0x54, 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x55, + 0x50, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x49, 0x4d, 0x41, + 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, + 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x4d, 0x54, 0x50, 0x5f, 0x50, 0x4f, + 0x52, 0x54, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, + 0x03, 0x12, 0x25, 0x0a, 0x21, 0x49, 0x4d, 0x41, 0x50, 0x5f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, + 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, + 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x25, 0x0a, 0x21, 0x53, 0x4d, 0x54, 0x50, + 0x5f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x4f, 0x44, 0x45, + 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x2a, + 0x53, 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x11, 0x0a, 0x0d, + 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, + 0x19, 0x0a, 0x15, 0x54, 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, 0xfc, 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, 0x42, 0x6f, 0x6f, 0x6c, 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, 0x49, 0x0a, 0x13, 0x49, 0x73, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, - 0x79, 0x44, 0x69, 0x73, 0x61, 0x62, 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, 0x3c, 0x0a, - 0x04, 0x47, 0x6f, 0x4f, 0x73, 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, 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, 0x12, 0x3e, 0x0a, 0x0c, 0x54, - 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x73, 0x65, 0x74, 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, 0x07, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 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, 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, 0x12, 0x40, 0x0a, 0x08, - 0x4c, 0x6f, 0x67, 0x73, 0x50, 0x61, 0x74, 0x68, 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, 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, 0x12, 0x43, - 0x0a, 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x50, 0x61, 0x74, 0x68, 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, 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, 0x12, 0x4c, 0x0a, 0x14, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4e, 0x6f, - 0x74, 0x65, 0x73, 0x50, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 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, 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, 0x12, 0x4e, 0x0a, 0x16, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x4c, - 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x4c, 0x69, 0x6e, 0x6b, 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, 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, 0x12, 0x47, 0x0a, 0x0f, 0x4c, 0x61, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x67, 0x65, - 0x4c, 0x69, 0x6e, 0x6b, 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, 0x1c, 0x2e, 0x67, + 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 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, 0x12, 0x4a, 0x0a, 0x12, 0x53, 0x65, - 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 0x65, - 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, 0x47, 0x0a, 0x0f, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x53, - 0x63, 0x68, 0x65, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 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, 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, 0x12, - 0x4a, 0x0a, 0x12, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x43, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 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, 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, 0x12, 0x3b, 0x0a, 0x09, 0x52, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x12, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 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, 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, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x41, 0x64, + 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x41, 0x64, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 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, 0x3a, 0x0a, 0x08, 0x47, + 0x75, 0x69, 0x52, 0x65, 0x61, 0x64, 0x79, 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, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x75, 0x69, 0x52, 0x65, 0x61, 0x64, 0x79, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x51, 0x75, 0x69, 0x74, 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, 0x45, 0x0a, 0x0d, 0x46, 0x6f, 0x72, 0x63, 0x65, - 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x72, 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, 0x49, - 0x0a, 0x11, 0x53, 0x65, 0x74, 0x4d, 0x61, 0x69, 0x6e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x61, - 0x62, 0x6c, 0x65, 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, 0x33, 0x0a, 0x05, 0x4c, 0x6f, 0x67, - 0x69, 0x6e, 0x12, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 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, 0x36, - 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x32, 0x46, 0x41, 0x12, 0x12, 0x2e, 0x67, 0x72, 0x70, - 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 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, 0x3d, 0x0a, 0x0f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x32, - 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, - 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 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, 0x3d, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x41, 0x62, - 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, - 0x41, 0x62, 0x6f, 0x72, 0x74, 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, 0x3d, 0x0a, 0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x55, 0x70, 0x64, - 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, 0x3f, 0x0a, 0x0d, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x55, 0x70, - 0x64, 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, 0x4c, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x49, 0x73, 0x41, 0x75, 0x74, - 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4f, 0x6e, 0x12, 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, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 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, + 0x39, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 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, 0x49, 0x0a, 0x13, 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, - 0x63, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4f, 0x6e, 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, 0x45, 0x0a, - 0x0d, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 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, 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, 0x12, 0x48, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x44, 0x69, 0x73, 0x6b, 0x43, - 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 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, 0x45, - 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x49, 0x73, 0x44, 0x6f, 0x48, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, - 0x64, 0x12, 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, 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, 0x0c, 0x49, 0x73, 0x44, 0x6f, 0x48, 0x45, 0x6e, + 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, 0x43, 0x0a, 0x0d, 0x53, 0x68, + 0x6f, 0x77, 0x4f, 0x6e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 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, + 0x46, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x4f, 0x6e, 0x12, 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, 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, 0x43, 0x0a, 0x0d, 0x49, 0x73, 0x41, 0x75, 0x74, + 0x6f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4f, 0x6e, 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, 0x46, 0x0a, 0x10, + 0x53, 0x65, 0x74, 0x49, 0x73, 0x42, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x12, 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, 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, 0x43, 0x0a, 0x0d, 0x49, 0x73, 0x42, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x61, 0x62, 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, 0x44, 0x0a, 0x12, 0x4d, 0x61, 0x69, - 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 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, 0x72, 0x70, 0x63, 0x2e, 0x49, - 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, - 0x47, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, - 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, - 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, 0x40, 0x0a, 0x08, 0x48, 0x6f, 0x73, 0x74, - 0x6e, 0x61, 0x6d, 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, 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, 0x12, 0x45, 0x0a, 0x0a, 0x49, 0x73, - 0x50, 0x6f, 0x72, 0x74, 0x46, 0x72, 0x65, 0x65, 0x12, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, - 0x56, 0x61, 0x6c, 0x75, 0x65, 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, 0x4e, 0x0a, 0x12, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x4b, 0x65, - 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 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, - 0x20, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, - 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4b, - 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 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, 0x47, 0x0a, - 0x0f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x49, 0x0a, 0x13, 0x53, 0x65, 0x74, + 0x49, 0x73, 0x41, 0x6c, 0x6c, 0x4d, 0x61, 0x69, 0x6c, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, + 0x12, 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, 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, 0x46, 0x0a, 0x10, 0x49, 0x73, 0x41, 0x6c, 0x6c, 0x4d, 0x61, 0x69, + 0x6c, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 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, 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, 0x4c, 0x0a, 0x16, + 0x53, 0x65, 0x74, 0x49, 0x73, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x44, 0x69, + 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 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, 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, 0x13, 0x49, 0x73, + 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x44, 0x69, 0x73, 0x61, 0x62, 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, 0x3c, 0x0a, 0x04, 0x47, 0x6f, 0x4f, 0x73, 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, 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, 0x12, 0x3e, 0x0a, 0x0c, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x65, 0x74, 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, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 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, 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, 0x12, 0x40, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x73, 0x50, 0x61, 0x74, 0x68, 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, 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, 0x12, 0x3d, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x4c, 0x69, 0x73, 0x74, 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, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, - 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, 0x0a, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x12, 0x46, 0x0a, 0x10, 0x53, 0x65, - 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1a, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, - 0x6f, 0x64, 0x65, 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, 0x55, 0x0a, 0x18, 0x53, 0x65, 0x6e, 0x64, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x55, 0x73, 0x65, 0x72, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x21, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 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, 0x0a, 0x4c, 0x6f, 0x67, - 0x6f, 0x75, 0x74, 0x55, 0x73, 0x65, 0x72, 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, 0x42, 0x0a, - 0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 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, 0x51, 0x0a, 0x16, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x55, 0x73, - 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x12, 0x1f, 0x2e, 0x67, 0x72, - 0x70, 0x63, 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, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x43, 0x0a, 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, + 0x65, 0x50, 0x61, 0x74, 0x68, 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, 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, 0x48, 0x0a, 0x10, 0x4b, 0x42, 0x41, 0x72, 0x74, 0x69, 0x63, 0x6c, 0x65, - 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, 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, + 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4c, 0x0a, 0x14, 0x52, + 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x73, 0x50, 0x61, 0x67, 0x65, 0x4c, + 0x69, 0x6e, 0x6b, 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, 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, 0x12, 0x4e, 0x0a, 0x16, 0x44, 0x65, 0x70, + 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x4c, + 0x69, 0x6e, 0x6b, 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, 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, 0x12, 0x47, 0x0a, 0x0f, 0x4c, 0x61, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 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, 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, 0x12, 0x4a, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x53, 0x63, + 0x68, 0x65, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 0x65, 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, 0x47, + 0x0a, 0x0f, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x4e, 0x61, 0x6d, + 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, 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, 0x12, 0x4a, 0x0a, 0x12, 0x43, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x74, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 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, 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, 0x12, 0x3b, 0x0a, 0x09, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, + 0x12, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, + 0x67, 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, 0x45, 0x0a, 0x0d, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, + 0x72, 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, 0x49, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x4d, 0x61, + 0x69, 0x6e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 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, 0x33, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x12, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 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, 0x36, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x69, 0x6e, + 0x32, 0x46, 0x41, 0x12, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, + 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, + 0x3d, 0x0a, 0x0f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x32, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, + 0x64, 0x73, 0x12, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 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, 0x3d, + 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x41, 0x62, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x41, 0x62, 0x6f, 0x72, 0x74, 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, 0x3d, 0x0a, + 0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x55, 0x70, 0x64, 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, 0x3f, 0x0a, 0x0d, + 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x55, 0x70, 0x64, 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, 0x4c, 0x0a, + 0x16, 0x53, 0x65, 0x74, 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x4f, 0x6e, 0x12, 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, 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, 0x13, 0x49, + 0x73, 0x41, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x4f, 0x6e, 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, 0x45, 0x0a, 0x0d, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, + 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 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, + 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, 0x12, 0x48, 0x0a, + 0x10, 0x53, 0x65, 0x74, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, + 0x68, 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, 0x45, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x49, 0x73, + 0x44, 0x6f, 0x48, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 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, 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, 0x0c, 0x49, 0x73, 0x44, 0x6f, 0x48, 0x45, 0x6e, 0x61, 0x62, 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, 0x44, 0x0a, 0x12, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 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, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, + 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x47, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x4d, + 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, + 0x73, 0x12, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, + 0x70, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 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, 0x40, 0x0a, 0x08, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 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, 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, 0x12, 0x45, 0x0a, 0x0a, 0x49, 0x73, 0x50, 0x6f, 0x72, 0x74, 0x46, 0x72, 0x65, + 0x65, 0x12, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 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, 0x4e, 0x0a, 0x12, 0x41, 0x76, + 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, + 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, 0x20, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, + 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x53, 0x65, + 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, + 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, 0x47, 0x0a, 0x0f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x74, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 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, 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, 0x12, + 0x3d, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 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, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, + 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, + 0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 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, 0x0a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, + 0x73, 0x65, 0x72, 0x12, 0x46, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x70, + 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, + 0x73, 0x65, 0x72, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 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, 0x55, 0x0a, 0x18, 0x53, + 0x65, 0x6e, 0x64, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x73, 0x65, 0x72, 0x46, + 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x21, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, + 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x46, 0x65, 0x65, 0x64, 0x62, + 0x61, 0x63, 0x6b, 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, 0x0a, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x55, 0x73, 0x65, 0x72, + 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, 0x42, 0x0a, 0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, + 0x55, 0x73, 0x65, 0x72, 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, 0x51, 0x0a, 0x16, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x65, + 0x4d, 0x61, 0x69, 0x6c, 0x12, 0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 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, 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, + 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, 0x48, 0x0a, 0x10, + 0x4b, 0x42, 0x41, 0x72, 0x74, 0x69, 0x63, 0x6c, 0x65, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x65, 0x64, + 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4f, 0x0a, 0x19, 0x49, 0x73, 0x54, 0x4c, 0x53, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, + 0x6c, 0x65, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, + 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x47, 0x0a, 0x15, 0x49, 0x6e, 0x73, 0x74, 0x61, + 0x6c, 0x6c, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, + 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x12, 0x4d, 0x0a, 0x15, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 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, 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 ( @@ -5154,7 +5345,7 @@ func file_bridge_proto_rawDescGZIP() []byte { } var file_bridge_proto_enumTypes = make([]protoimpl.EnumInfo, 7) -var file_bridge_proto_msgTypes = make([]protoimpl.MessageInfo, 65) +var file_bridge_proto_msgTypes = make([]protoimpl.MessageInfo, 68) var file_bridge_proto_goTypes = []interface{}{ (LogLevel)(0), // 0: grpc.LogLevel (UserState)(0), // 1: grpc.UserState @@ -5186,66 +5377,69 @@ var file_bridge_proto_goTypes = []interface{}{ (*ReportBugErrorEvent)(nil), // 27: grpc.ReportBugErrorEvent (*ShowMainWindowEvent)(nil), // 28: grpc.ShowMainWindowEvent (*ReportBugFallbackEvent)(nil), // 29: grpc.ReportBugFallbackEvent - (*LoginEvent)(nil), // 30: grpc.LoginEvent - (*LoginErrorEvent)(nil), // 31: grpc.LoginErrorEvent - (*LoginTfaRequestedEvent)(nil), // 32: grpc.LoginTfaRequestedEvent - (*LoginTwoPasswordsRequestedEvent)(nil), // 33: grpc.LoginTwoPasswordsRequestedEvent - (*LoginFinishedEvent)(nil), // 34: grpc.LoginFinishedEvent - (*UpdateEvent)(nil), // 35: grpc.UpdateEvent - (*UpdateErrorEvent)(nil), // 36: grpc.UpdateErrorEvent - (*UpdateManualReadyEvent)(nil), // 37: grpc.UpdateManualReadyEvent - (*UpdateManualRestartNeededEvent)(nil), // 38: grpc.UpdateManualRestartNeededEvent - (*UpdateForceEvent)(nil), // 39: grpc.UpdateForceEvent - (*UpdateSilentRestartNeeded)(nil), // 40: grpc.UpdateSilentRestartNeeded - (*UpdateIsLatestVersion)(nil), // 41: grpc.UpdateIsLatestVersion - (*UpdateCheckFinished)(nil), // 42: grpc.UpdateCheckFinished - (*UpdateVersionChanged)(nil), // 43: grpc.UpdateVersionChanged - (*DiskCacheEvent)(nil), // 44: grpc.DiskCacheEvent - (*DiskCacheErrorEvent)(nil), // 45: grpc.DiskCacheErrorEvent - (*DiskCachePathChangedEvent)(nil), // 46: grpc.DiskCachePathChangedEvent - (*DiskCachePathChangeFinishedEvent)(nil), // 47: grpc.DiskCachePathChangeFinishedEvent - (*MailServerSettingsEvent)(nil), // 48: grpc.MailServerSettingsEvent - (*MailServerSettingsErrorEvent)(nil), // 49: grpc.MailServerSettingsErrorEvent - (*MailServerSettingsChangedEvent)(nil), // 50: grpc.MailServerSettingsChangedEvent - (*ChangeMailServerSettingsFinishedEvent)(nil), // 51: grpc.ChangeMailServerSettingsFinishedEvent - (*KeychainEvent)(nil), // 52: grpc.KeychainEvent - (*ChangeKeychainFinishedEvent)(nil), // 53: grpc.ChangeKeychainFinishedEvent - (*HasNoKeychainEvent)(nil), // 54: grpc.HasNoKeychainEvent - (*RebuildKeychainEvent)(nil), // 55: grpc.RebuildKeychainEvent - (*MailEvent)(nil), // 56: grpc.MailEvent - (*NoActiveKeyForRecipientEvent)(nil), // 57: grpc.NoActiveKeyForRecipientEvent - (*AddressChangedEvent)(nil), // 58: grpc.AddressChangedEvent - (*AddressChangedLogoutEvent)(nil), // 59: grpc.AddressChangedLogoutEvent - (*ApiCertIssueEvent)(nil), // 60: grpc.ApiCertIssueEvent - (*UserEvent)(nil), // 61: grpc.UserEvent - (*ToggleSplitModeFinishedEvent)(nil), // 62: grpc.ToggleSplitModeFinishedEvent - (*UserDisconnectedEvent)(nil), // 63: grpc.UserDisconnectedEvent - (*UserChangedEvent)(nil), // 64: grpc.UserChangedEvent - (*UserBadEvent)(nil), // 65: grpc.UserBadEvent - (*UsedBytesChangedEvent)(nil), // 66: grpc.UsedBytesChangedEvent - (*ImapLoginFailedEvent)(nil), // 67: grpc.ImapLoginFailedEvent - (*SyncStartedEvent)(nil), // 68: grpc.SyncStartedEvent - (*SyncFinishedEvent)(nil), // 69: grpc.SyncFinishedEvent - (*SyncProgressEvent)(nil), // 70: grpc.SyncProgressEvent - (*GenericErrorEvent)(nil), // 71: grpc.GenericErrorEvent - (*wrapperspb.StringValue)(nil), // 72: google.protobuf.StringValue - (*emptypb.Empty)(nil), // 73: google.protobuf.Empty - (*wrapperspb.BoolValue)(nil), // 74: google.protobuf.BoolValue - (*wrapperspb.Int32Value)(nil), // 75: google.protobuf.Int32Value + (*CertificateInstallSuccessEvent)(nil), // 30: grpc.CertificateInstallSuccessEvent + (*CertificateInstallCanceledEvent)(nil), // 31: grpc.CertificateInstallCanceledEvent + (*CertificateInstallFailedEvent)(nil), // 32: grpc.CertificateInstallFailedEvent + (*LoginEvent)(nil), // 33: grpc.LoginEvent + (*LoginErrorEvent)(nil), // 34: grpc.LoginErrorEvent + (*LoginTfaRequestedEvent)(nil), // 35: grpc.LoginTfaRequestedEvent + (*LoginTwoPasswordsRequestedEvent)(nil), // 36: grpc.LoginTwoPasswordsRequestedEvent + (*LoginFinishedEvent)(nil), // 37: grpc.LoginFinishedEvent + (*UpdateEvent)(nil), // 38: grpc.UpdateEvent + (*UpdateErrorEvent)(nil), // 39: grpc.UpdateErrorEvent + (*UpdateManualReadyEvent)(nil), // 40: grpc.UpdateManualReadyEvent + (*UpdateManualRestartNeededEvent)(nil), // 41: grpc.UpdateManualRestartNeededEvent + (*UpdateForceEvent)(nil), // 42: grpc.UpdateForceEvent + (*UpdateSilentRestartNeeded)(nil), // 43: grpc.UpdateSilentRestartNeeded + (*UpdateIsLatestVersion)(nil), // 44: grpc.UpdateIsLatestVersion + (*UpdateCheckFinished)(nil), // 45: grpc.UpdateCheckFinished + (*UpdateVersionChanged)(nil), // 46: grpc.UpdateVersionChanged + (*DiskCacheEvent)(nil), // 47: grpc.DiskCacheEvent + (*DiskCacheErrorEvent)(nil), // 48: grpc.DiskCacheErrorEvent + (*DiskCachePathChangedEvent)(nil), // 49: grpc.DiskCachePathChangedEvent + (*DiskCachePathChangeFinishedEvent)(nil), // 50: grpc.DiskCachePathChangeFinishedEvent + (*MailServerSettingsEvent)(nil), // 51: grpc.MailServerSettingsEvent + (*MailServerSettingsErrorEvent)(nil), // 52: grpc.MailServerSettingsErrorEvent + (*MailServerSettingsChangedEvent)(nil), // 53: grpc.MailServerSettingsChangedEvent + (*ChangeMailServerSettingsFinishedEvent)(nil), // 54: grpc.ChangeMailServerSettingsFinishedEvent + (*KeychainEvent)(nil), // 55: grpc.KeychainEvent + (*ChangeKeychainFinishedEvent)(nil), // 56: grpc.ChangeKeychainFinishedEvent + (*HasNoKeychainEvent)(nil), // 57: grpc.HasNoKeychainEvent + (*RebuildKeychainEvent)(nil), // 58: grpc.RebuildKeychainEvent + (*MailEvent)(nil), // 59: grpc.MailEvent + (*NoActiveKeyForRecipientEvent)(nil), // 60: grpc.NoActiveKeyForRecipientEvent + (*AddressChangedEvent)(nil), // 61: grpc.AddressChangedEvent + (*AddressChangedLogoutEvent)(nil), // 62: grpc.AddressChangedLogoutEvent + (*ApiCertIssueEvent)(nil), // 63: grpc.ApiCertIssueEvent + (*UserEvent)(nil), // 64: grpc.UserEvent + (*ToggleSplitModeFinishedEvent)(nil), // 65: grpc.ToggleSplitModeFinishedEvent + (*UserDisconnectedEvent)(nil), // 66: grpc.UserDisconnectedEvent + (*UserChangedEvent)(nil), // 67: grpc.UserChangedEvent + (*UserBadEvent)(nil), // 68: grpc.UserBadEvent + (*UsedBytesChangedEvent)(nil), // 69: grpc.UsedBytesChangedEvent + (*ImapLoginFailedEvent)(nil), // 70: grpc.ImapLoginFailedEvent + (*SyncStartedEvent)(nil), // 71: grpc.SyncStartedEvent + (*SyncFinishedEvent)(nil), // 72: grpc.SyncFinishedEvent + (*SyncProgressEvent)(nil), // 73: grpc.SyncProgressEvent + (*GenericErrorEvent)(nil), // 74: grpc.GenericErrorEvent + (*wrapperspb.StringValue)(nil), // 75: google.protobuf.StringValue + (*emptypb.Empty)(nil), // 76: google.protobuf.Empty + (*wrapperspb.BoolValue)(nil), // 77: google.protobuf.BoolValue + (*wrapperspb.Int32Value)(nil), // 78: google.protobuf.Int32Value } var file_bridge_proto_depIdxs = []int32{ 0, // 0: grpc.AddLogEntryRequest.level:type_name -> grpc.LogLevel 1, // 1: grpc.User.state:type_name -> grpc.UserState 14, // 2: grpc.UserListResponse.users:type_name -> grpc.User 21, // 3: grpc.StreamEvent.app:type_name -> grpc.AppEvent - 30, // 4: grpc.StreamEvent.login:type_name -> grpc.LoginEvent - 35, // 5: grpc.StreamEvent.update:type_name -> grpc.UpdateEvent - 44, // 6: grpc.StreamEvent.cache:type_name -> grpc.DiskCacheEvent - 48, // 7: grpc.StreamEvent.mailServerSettings:type_name -> grpc.MailServerSettingsEvent - 52, // 8: grpc.StreamEvent.keychain:type_name -> grpc.KeychainEvent - 56, // 9: grpc.StreamEvent.mail:type_name -> grpc.MailEvent - 61, // 10: grpc.StreamEvent.user:type_name -> grpc.UserEvent - 71, // 11: grpc.StreamEvent.genericError:type_name -> grpc.GenericErrorEvent + 33, // 4: grpc.StreamEvent.login:type_name -> grpc.LoginEvent + 38, // 5: grpc.StreamEvent.update:type_name -> grpc.UpdateEvent + 47, // 6: grpc.StreamEvent.cache:type_name -> grpc.DiskCacheEvent + 51, // 7: grpc.StreamEvent.mailServerSettings:type_name -> grpc.MailServerSettingsEvent + 55, // 8: grpc.StreamEvent.keychain:type_name -> grpc.KeychainEvent + 59, // 9: grpc.StreamEvent.mail:type_name -> grpc.MailEvent + 64, // 10: grpc.StreamEvent.user:type_name -> grpc.UserEvent + 74, // 11: grpc.StreamEvent.genericError:type_name -> grpc.GenericErrorEvent 22, // 12: grpc.AppEvent.internetStatus:type_name -> grpc.InternetStatusEvent 23, // 13: grpc.AppEvent.toggleAutostartFinished:type_name -> grpc.ToggleAutostartFinishedEvent 24, // 14: grpc.AppEvent.resetFinished:type_name -> grpc.ResetFinishedEvent @@ -5254,172 +5448,179 @@ var file_bridge_proto_depIdxs = []int32{ 27, // 17: grpc.AppEvent.reportBugError:type_name -> grpc.ReportBugErrorEvent 28, // 18: grpc.AppEvent.showMainWindow:type_name -> grpc.ShowMainWindowEvent 29, // 19: grpc.AppEvent.reportBugFallback:type_name -> grpc.ReportBugFallbackEvent - 31, // 20: grpc.LoginEvent.error:type_name -> grpc.LoginErrorEvent - 32, // 21: grpc.LoginEvent.tfaRequested:type_name -> grpc.LoginTfaRequestedEvent - 33, // 22: grpc.LoginEvent.twoPasswordRequested:type_name -> grpc.LoginTwoPasswordsRequestedEvent - 34, // 23: grpc.LoginEvent.finished:type_name -> grpc.LoginFinishedEvent - 34, // 24: grpc.LoginEvent.alreadyLoggedIn:type_name -> grpc.LoginFinishedEvent - 2, // 25: grpc.LoginErrorEvent.type:type_name -> grpc.LoginErrorType - 36, // 26: grpc.UpdateEvent.error:type_name -> grpc.UpdateErrorEvent - 37, // 27: grpc.UpdateEvent.manualReady:type_name -> grpc.UpdateManualReadyEvent - 38, // 28: grpc.UpdateEvent.manualRestartNeeded:type_name -> grpc.UpdateManualRestartNeededEvent - 39, // 29: grpc.UpdateEvent.force:type_name -> grpc.UpdateForceEvent - 40, // 30: grpc.UpdateEvent.silentRestartNeeded:type_name -> grpc.UpdateSilentRestartNeeded - 41, // 31: grpc.UpdateEvent.isLatestVersion:type_name -> grpc.UpdateIsLatestVersion - 42, // 32: grpc.UpdateEvent.checkFinished:type_name -> grpc.UpdateCheckFinished - 43, // 33: grpc.UpdateEvent.versionChanged:type_name -> grpc.UpdateVersionChanged - 3, // 34: grpc.UpdateErrorEvent.type:type_name -> grpc.UpdateErrorType - 45, // 35: grpc.DiskCacheEvent.error:type_name -> grpc.DiskCacheErrorEvent - 46, // 36: grpc.DiskCacheEvent.pathChanged:type_name -> grpc.DiskCachePathChangedEvent - 47, // 37: grpc.DiskCacheEvent.pathChangeFinished:type_name -> grpc.DiskCachePathChangeFinishedEvent - 4, // 38: grpc.DiskCacheErrorEvent.type:type_name -> grpc.DiskCacheErrorType - 49, // 39: grpc.MailServerSettingsEvent.error:type_name -> grpc.MailServerSettingsErrorEvent - 50, // 40: grpc.MailServerSettingsEvent.mailServerSettingsChanged:type_name -> grpc.MailServerSettingsChangedEvent - 51, // 41: grpc.MailServerSettingsEvent.changeMailServerSettingsFinished:type_name -> grpc.ChangeMailServerSettingsFinishedEvent - 5, // 42: grpc.MailServerSettingsErrorEvent.type:type_name -> grpc.MailServerSettingsErrorType - 12, // 43: grpc.MailServerSettingsChangedEvent.settings:type_name -> grpc.ImapSmtpSettings - 53, // 44: grpc.KeychainEvent.changeKeychainFinished:type_name -> grpc.ChangeKeychainFinishedEvent - 54, // 45: grpc.KeychainEvent.hasNoKeychain:type_name -> grpc.HasNoKeychainEvent - 55, // 46: grpc.KeychainEvent.rebuildKeychain:type_name -> grpc.RebuildKeychainEvent - 57, // 47: grpc.MailEvent.noActiveKeyForRecipientEvent:type_name -> grpc.NoActiveKeyForRecipientEvent - 58, // 48: grpc.MailEvent.addressChanged:type_name -> grpc.AddressChangedEvent - 59, // 49: grpc.MailEvent.addressChangedLogout:type_name -> grpc.AddressChangedLogoutEvent - 60, // 50: grpc.MailEvent.apiCertIssue:type_name -> grpc.ApiCertIssueEvent - 62, // 51: grpc.UserEvent.toggleSplitModeFinished:type_name -> grpc.ToggleSplitModeFinishedEvent - 63, // 52: grpc.UserEvent.userDisconnected:type_name -> grpc.UserDisconnectedEvent - 64, // 53: grpc.UserEvent.userChanged:type_name -> grpc.UserChangedEvent - 65, // 54: grpc.UserEvent.userBadEvent:type_name -> grpc.UserBadEvent - 66, // 55: grpc.UserEvent.usedBytesChangedEvent:type_name -> grpc.UsedBytesChangedEvent - 67, // 56: grpc.UserEvent.imapLoginFailedEvent:type_name -> grpc.ImapLoginFailedEvent - 68, // 57: grpc.UserEvent.syncStartedEvent:type_name -> grpc.SyncStartedEvent - 69, // 58: grpc.UserEvent.syncFinishedEvent:type_name -> grpc.SyncFinishedEvent - 70, // 59: grpc.UserEvent.syncProgressEvent:type_name -> grpc.SyncProgressEvent - 6, // 60: grpc.GenericErrorEvent.code:type_name -> grpc.ErrorCode - 72, // 61: grpc.Bridge.CheckTokens:input_type -> google.protobuf.StringValue - 7, // 62: grpc.Bridge.AddLogEntry:input_type -> grpc.AddLogEntryRequest - 73, // 63: grpc.Bridge.GuiReady:input_type -> google.protobuf.Empty - 73, // 64: grpc.Bridge.Quit:input_type -> google.protobuf.Empty - 73, // 65: grpc.Bridge.Restart:input_type -> google.protobuf.Empty - 73, // 66: grpc.Bridge.ShowOnStartup:input_type -> google.protobuf.Empty - 74, // 67: grpc.Bridge.SetIsAutostartOn:input_type -> google.protobuf.BoolValue - 73, // 68: grpc.Bridge.IsAutostartOn:input_type -> google.protobuf.Empty - 74, // 69: grpc.Bridge.SetIsBetaEnabled:input_type -> google.protobuf.BoolValue - 73, // 70: grpc.Bridge.IsBetaEnabled:input_type -> google.protobuf.Empty - 74, // 71: grpc.Bridge.SetIsAllMailVisible:input_type -> google.protobuf.BoolValue - 73, // 72: grpc.Bridge.IsAllMailVisible:input_type -> google.protobuf.Empty - 74, // 73: grpc.Bridge.SetIsTelemetryDisabled:input_type -> google.protobuf.BoolValue - 73, // 74: grpc.Bridge.IsTelemetryDisabled:input_type -> google.protobuf.Empty - 73, // 75: grpc.Bridge.GoOs:input_type -> google.protobuf.Empty - 73, // 76: grpc.Bridge.TriggerReset:input_type -> google.protobuf.Empty - 73, // 77: grpc.Bridge.Version:input_type -> google.protobuf.Empty - 73, // 78: grpc.Bridge.LogsPath:input_type -> google.protobuf.Empty - 73, // 79: grpc.Bridge.LicensePath:input_type -> google.protobuf.Empty - 73, // 80: grpc.Bridge.ReleaseNotesPageLink:input_type -> google.protobuf.Empty - 73, // 81: grpc.Bridge.DependencyLicensesLink:input_type -> google.protobuf.Empty - 73, // 82: grpc.Bridge.LandingPageLink:input_type -> google.protobuf.Empty - 72, // 83: grpc.Bridge.SetColorSchemeName:input_type -> google.protobuf.StringValue - 73, // 84: grpc.Bridge.ColorSchemeName:input_type -> google.protobuf.Empty - 73, // 85: grpc.Bridge.CurrentEmailClient:input_type -> google.protobuf.Empty - 9, // 86: grpc.Bridge.ReportBug:input_type -> grpc.ReportBugRequest - 72, // 87: grpc.Bridge.ExportTLSCertificates:input_type -> google.protobuf.StringValue - 72, // 88: grpc.Bridge.ForceLauncher:input_type -> google.protobuf.StringValue - 72, // 89: grpc.Bridge.SetMainExecutable:input_type -> google.protobuf.StringValue - 10, // 90: grpc.Bridge.Login:input_type -> grpc.LoginRequest - 10, // 91: grpc.Bridge.Login2FA:input_type -> grpc.LoginRequest - 10, // 92: grpc.Bridge.Login2Passwords:input_type -> grpc.LoginRequest - 11, // 93: grpc.Bridge.LoginAbort:input_type -> grpc.LoginAbortRequest - 73, // 94: grpc.Bridge.CheckUpdate:input_type -> google.protobuf.Empty - 73, // 95: grpc.Bridge.InstallUpdate:input_type -> google.protobuf.Empty - 74, // 96: grpc.Bridge.SetIsAutomaticUpdateOn:input_type -> google.protobuf.BoolValue - 73, // 97: grpc.Bridge.IsAutomaticUpdateOn:input_type -> google.protobuf.Empty - 73, // 98: grpc.Bridge.DiskCachePath:input_type -> google.protobuf.Empty - 72, // 99: grpc.Bridge.SetDiskCachePath:input_type -> google.protobuf.StringValue - 74, // 100: grpc.Bridge.SetIsDoHEnabled:input_type -> google.protobuf.BoolValue - 73, // 101: grpc.Bridge.IsDoHEnabled:input_type -> google.protobuf.Empty - 73, // 102: grpc.Bridge.MailServerSettings:input_type -> google.protobuf.Empty - 12, // 103: grpc.Bridge.SetMailServerSettings:input_type -> grpc.ImapSmtpSettings - 73, // 104: grpc.Bridge.Hostname:input_type -> google.protobuf.Empty - 75, // 105: grpc.Bridge.IsPortFree:input_type -> google.protobuf.Int32Value - 73, // 106: grpc.Bridge.AvailableKeychains:input_type -> google.protobuf.Empty - 72, // 107: grpc.Bridge.SetCurrentKeychain:input_type -> google.protobuf.StringValue - 73, // 108: grpc.Bridge.CurrentKeychain:input_type -> google.protobuf.Empty - 73, // 109: grpc.Bridge.GetUserList:input_type -> google.protobuf.Empty - 72, // 110: grpc.Bridge.GetUser:input_type -> google.protobuf.StringValue - 15, // 111: grpc.Bridge.SetUserSplitMode:input_type -> grpc.UserSplitModeRequest - 16, // 112: grpc.Bridge.SendBadEventUserFeedback:input_type -> grpc.UserBadEventFeedbackRequest - 72, // 113: grpc.Bridge.LogoutUser:input_type -> google.protobuf.StringValue - 72, // 114: grpc.Bridge.RemoveUser:input_type -> google.protobuf.StringValue - 18, // 115: grpc.Bridge.ConfigureUserAppleMail:input_type -> grpc.ConfigureAppleMailRequest - 73, // 116: grpc.Bridge.ReportBugClicked:input_type -> google.protobuf.Empty - 72, // 117: grpc.Bridge.AutoconfigClicked:input_type -> google.protobuf.StringValue - 72, // 118: grpc.Bridge.KBArticleClicked:input_type -> google.protobuf.StringValue - 19, // 119: grpc.Bridge.RunEventStream:input_type -> grpc.EventStreamRequest - 73, // 120: grpc.Bridge.StopEventStream:input_type -> google.protobuf.Empty - 72, // 121: grpc.Bridge.CheckTokens:output_type -> google.protobuf.StringValue - 73, // 122: grpc.Bridge.AddLogEntry:output_type -> google.protobuf.Empty - 8, // 123: grpc.Bridge.GuiReady:output_type -> grpc.GuiReadyResponse - 73, // 124: grpc.Bridge.Quit:output_type -> google.protobuf.Empty - 73, // 125: grpc.Bridge.Restart:output_type -> google.protobuf.Empty - 74, // 126: grpc.Bridge.ShowOnStartup:output_type -> google.protobuf.BoolValue - 73, // 127: grpc.Bridge.SetIsAutostartOn:output_type -> google.protobuf.Empty - 74, // 128: grpc.Bridge.IsAutostartOn:output_type -> google.protobuf.BoolValue - 73, // 129: grpc.Bridge.SetIsBetaEnabled:output_type -> google.protobuf.Empty - 74, // 130: grpc.Bridge.IsBetaEnabled:output_type -> google.protobuf.BoolValue - 73, // 131: grpc.Bridge.SetIsAllMailVisible:output_type -> google.protobuf.Empty - 74, // 132: grpc.Bridge.IsAllMailVisible:output_type -> google.protobuf.BoolValue - 73, // 133: grpc.Bridge.SetIsTelemetryDisabled:output_type -> google.protobuf.Empty - 74, // 134: grpc.Bridge.IsTelemetryDisabled:output_type -> google.protobuf.BoolValue - 72, // 135: grpc.Bridge.GoOs:output_type -> google.protobuf.StringValue - 73, // 136: grpc.Bridge.TriggerReset:output_type -> google.protobuf.Empty - 72, // 137: grpc.Bridge.Version:output_type -> google.protobuf.StringValue - 72, // 138: grpc.Bridge.LogsPath:output_type -> google.protobuf.StringValue - 72, // 139: grpc.Bridge.LicensePath:output_type -> google.protobuf.StringValue - 72, // 140: grpc.Bridge.ReleaseNotesPageLink:output_type -> google.protobuf.StringValue - 72, // 141: grpc.Bridge.DependencyLicensesLink:output_type -> google.protobuf.StringValue - 72, // 142: grpc.Bridge.LandingPageLink:output_type -> google.protobuf.StringValue - 73, // 143: grpc.Bridge.SetColorSchemeName:output_type -> google.protobuf.Empty - 72, // 144: grpc.Bridge.ColorSchemeName:output_type -> google.protobuf.StringValue - 72, // 145: grpc.Bridge.CurrentEmailClient:output_type -> google.protobuf.StringValue - 73, // 146: grpc.Bridge.ReportBug:output_type -> google.protobuf.Empty - 73, // 147: grpc.Bridge.ExportTLSCertificates:output_type -> google.protobuf.Empty - 73, // 148: grpc.Bridge.ForceLauncher:output_type -> google.protobuf.Empty - 73, // 149: grpc.Bridge.SetMainExecutable:output_type -> google.protobuf.Empty - 73, // 150: grpc.Bridge.Login:output_type -> google.protobuf.Empty - 73, // 151: grpc.Bridge.Login2FA:output_type -> google.protobuf.Empty - 73, // 152: grpc.Bridge.Login2Passwords:output_type -> google.protobuf.Empty - 73, // 153: grpc.Bridge.LoginAbort:output_type -> google.protobuf.Empty - 73, // 154: grpc.Bridge.CheckUpdate:output_type -> google.protobuf.Empty - 73, // 155: grpc.Bridge.InstallUpdate:output_type -> google.protobuf.Empty - 73, // 156: grpc.Bridge.SetIsAutomaticUpdateOn:output_type -> google.protobuf.Empty - 74, // 157: grpc.Bridge.IsAutomaticUpdateOn:output_type -> google.protobuf.BoolValue - 72, // 158: grpc.Bridge.DiskCachePath:output_type -> google.protobuf.StringValue - 73, // 159: grpc.Bridge.SetDiskCachePath:output_type -> google.protobuf.Empty - 73, // 160: grpc.Bridge.SetIsDoHEnabled:output_type -> google.protobuf.Empty - 74, // 161: grpc.Bridge.IsDoHEnabled:output_type -> google.protobuf.BoolValue - 12, // 162: grpc.Bridge.MailServerSettings:output_type -> grpc.ImapSmtpSettings - 73, // 163: grpc.Bridge.SetMailServerSettings:output_type -> google.protobuf.Empty - 72, // 164: grpc.Bridge.Hostname:output_type -> google.protobuf.StringValue - 74, // 165: grpc.Bridge.IsPortFree:output_type -> google.protobuf.BoolValue - 13, // 166: grpc.Bridge.AvailableKeychains:output_type -> grpc.AvailableKeychainsResponse - 73, // 167: grpc.Bridge.SetCurrentKeychain:output_type -> google.protobuf.Empty - 72, // 168: grpc.Bridge.CurrentKeychain:output_type -> google.protobuf.StringValue - 17, // 169: grpc.Bridge.GetUserList:output_type -> grpc.UserListResponse - 14, // 170: grpc.Bridge.GetUser:output_type -> grpc.User - 73, // 171: grpc.Bridge.SetUserSplitMode:output_type -> google.protobuf.Empty - 73, // 172: grpc.Bridge.SendBadEventUserFeedback:output_type -> google.protobuf.Empty - 73, // 173: grpc.Bridge.LogoutUser:output_type -> google.protobuf.Empty - 73, // 174: grpc.Bridge.RemoveUser:output_type -> google.protobuf.Empty - 73, // 175: grpc.Bridge.ConfigureUserAppleMail:output_type -> google.protobuf.Empty - 73, // 176: grpc.Bridge.ReportBugClicked:output_type -> google.protobuf.Empty - 73, // 177: grpc.Bridge.AutoconfigClicked:output_type -> google.protobuf.Empty - 73, // 178: grpc.Bridge.KBArticleClicked:output_type -> google.protobuf.Empty - 20, // 179: grpc.Bridge.RunEventStream:output_type -> grpc.StreamEvent - 73, // 180: grpc.Bridge.StopEventStream:output_type -> google.protobuf.Empty - 121, // [121:181] is the sub-list for method output_type - 61, // [61:121] is the sub-list for method input_type - 61, // [61:61] is the sub-list for extension type_name - 61, // [61:61] is the sub-list for extension extendee - 0, // [0:61] is the sub-list for field type_name + 30, // 20: grpc.AppEvent.certificateInstallSuccess:type_name -> grpc.CertificateInstallSuccessEvent + 31, // 21: grpc.AppEvent.certificateInstallCanceled:type_name -> grpc.CertificateInstallCanceledEvent + 32, // 22: grpc.AppEvent.certificateInstallFailed:type_name -> grpc.CertificateInstallFailedEvent + 34, // 23: grpc.LoginEvent.error:type_name -> grpc.LoginErrorEvent + 35, // 24: grpc.LoginEvent.tfaRequested:type_name -> grpc.LoginTfaRequestedEvent + 36, // 25: grpc.LoginEvent.twoPasswordRequested:type_name -> grpc.LoginTwoPasswordsRequestedEvent + 37, // 26: grpc.LoginEvent.finished:type_name -> grpc.LoginFinishedEvent + 37, // 27: grpc.LoginEvent.alreadyLoggedIn:type_name -> grpc.LoginFinishedEvent + 2, // 28: grpc.LoginErrorEvent.type:type_name -> grpc.LoginErrorType + 39, // 29: grpc.UpdateEvent.error:type_name -> grpc.UpdateErrorEvent + 40, // 30: grpc.UpdateEvent.manualReady:type_name -> grpc.UpdateManualReadyEvent + 41, // 31: grpc.UpdateEvent.manualRestartNeeded:type_name -> grpc.UpdateManualRestartNeededEvent + 42, // 32: grpc.UpdateEvent.force:type_name -> grpc.UpdateForceEvent + 43, // 33: grpc.UpdateEvent.silentRestartNeeded:type_name -> grpc.UpdateSilentRestartNeeded + 44, // 34: grpc.UpdateEvent.isLatestVersion:type_name -> grpc.UpdateIsLatestVersion + 45, // 35: grpc.UpdateEvent.checkFinished:type_name -> grpc.UpdateCheckFinished + 46, // 36: grpc.UpdateEvent.versionChanged:type_name -> grpc.UpdateVersionChanged + 3, // 37: grpc.UpdateErrorEvent.type:type_name -> grpc.UpdateErrorType + 48, // 38: grpc.DiskCacheEvent.error:type_name -> grpc.DiskCacheErrorEvent + 49, // 39: grpc.DiskCacheEvent.pathChanged:type_name -> grpc.DiskCachePathChangedEvent + 50, // 40: grpc.DiskCacheEvent.pathChangeFinished:type_name -> grpc.DiskCachePathChangeFinishedEvent + 4, // 41: grpc.DiskCacheErrorEvent.type:type_name -> grpc.DiskCacheErrorType + 52, // 42: grpc.MailServerSettingsEvent.error:type_name -> grpc.MailServerSettingsErrorEvent + 53, // 43: grpc.MailServerSettingsEvent.mailServerSettingsChanged:type_name -> grpc.MailServerSettingsChangedEvent + 54, // 44: grpc.MailServerSettingsEvent.changeMailServerSettingsFinished:type_name -> grpc.ChangeMailServerSettingsFinishedEvent + 5, // 45: grpc.MailServerSettingsErrorEvent.type:type_name -> grpc.MailServerSettingsErrorType + 12, // 46: grpc.MailServerSettingsChangedEvent.settings:type_name -> grpc.ImapSmtpSettings + 56, // 47: grpc.KeychainEvent.changeKeychainFinished:type_name -> grpc.ChangeKeychainFinishedEvent + 57, // 48: grpc.KeychainEvent.hasNoKeychain:type_name -> grpc.HasNoKeychainEvent + 58, // 49: grpc.KeychainEvent.rebuildKeychain:type_name -> grpc.RebuildKeychainEvent + 60, // 50: grpc.MailEvent.noActiveKeyForRecipientEvent:type_name -> grpc.NoActiveKeyForRecipientEvent + 61, // 51: grpc.MailEvent.addressChanged:type_name -> grpc.AddressChangedEvent + 62, // 52: grpc.MailEvent.addressChangedLogout:type_name -> grpc.AddressChangedLogoutEvent + 63, // 53: grpc.MailEvent.apiCertIssue:type_name -> grpc.ApiCertIssueEvent + 65, // 54: grpc.UserEvent.toggleSplitModeFinished:type_name -> grpc.ToggleSplitModeFinishedEvent + 66, // 55: grpc.UserEvent.userDisconnected:type_name -> grpc.UserDisconnectedEvent + 67, // 56: grpc.UserEvent.userChanged:type_name -> grpc.UserChangedEvent + 68, // 57: grpc.UserEvent.userBadEvent:type_name -> grpc.UserBadEvent + 69, // 58: grpc.UserEvent.usedBytesChangedEvent:type_name -> grpc.UsedBytesChangedEvent + 70, // 59: grpc.UserEvent.imapLoginFailedEvent:type_name -> grpc.ImapLoginFailedEvent + 71, // 60: grpc.UserEvent.syncStartedEvent:type_name -> grpc.SyncStartedEvent + 72, // 61: grpc.UserEvent.syncFinishedEvent:type_name -> grpc.SyncFinishedEvent + 73, // 62: grpc.UserEvent.syncProgressEvent:type_name -> grpc.SyncProgressEvent + 6, // 63: grpc.GenericErrorEvent.code:type_name -> grpc.ErrorCode + 75, // 64: grpc.Bridge.CheckTokens:input_type -> google.protobuf.StringValue + 7, // 65: grpc.Bridge.AddLogEntry:input_type -> grpc.AddLogEntryRequest + 76, // 66: grpc.Bridge.GuiReady:input_type -> google.protobuf.Empty + 76, // 67: grpc.Bridge.Quit:input_type -> google.protobuf.Empty + 76, // 68: grpc.Bridge.Restart:input_type -> google.protobuf.Empty + 76, // 69: grpc.Bridge.ShowOnStartup:input_type -> google.protobuf.Empty + 77, // 70: grpc.Bridge.SetIsAutostartOn:input_type -> google.protobuf.BoolValue + 76, // 71: grpc.Bridge.IsAutostartOn:input_type -> google.protobuf.Empty + 77, // 72: grpc.Bridge.SetIsBetaEnabled:input_type -> google.protobuf.BoolValue + 76, // 73: grpc.Bridge.IsBetaEnabled:input_type -> google.protobuf.Empty + 77, // 74: grpc.Bridge.SetIsAllMailVisible:input_type -> google.protobuf.BoolValue + 76, // 75: grpc.Bridge.IsAllMailVisible:input_type -> google.protobuf.Empty + 77, // 76: grpc.Bridge.SetIsTelemetryDisabled:input_type -> google.protobuf.BoolValue + 76, // 77: grpc.Bridge.IsTelemetryDisabled:input_type -> google.protobuf.Empty + 76, // 78: grpc.Bridge.GoOs:input_type -> google.protobuf.Empty + 76, // 79: grpc.Bridge.TriggerReset:input_type -> google.protobuf.Empty + 76, // 80: grpc.Bridge.Version:input_type -> google.protobuf.Empty + 76, // 81: grpc.Bridge.LogsPath:input_type -> google.protobuf.Empty + 76, // 82: grpc.Bridge.LicensePath:input_type -> google.protobuf.Empty + 76, // 83: grpc.Bridge.ReleaseNotesPageLink:input_type -> google.protobuf.Empty + 76, // 84: grpc.Bridge.DependencyLicensesLink:input_type -> google.protobuf.Empty + 76, // 85: grpc.Bridge.LandingPageLink:input_type -> google.protobuf.Empty + 75, // 86: grpc.Bridge.SetColorSchemeName:input_type -> google.protobuf.StringValue + 76, // 87: grpc.Bridge.ColorSchemeName:input_type -> google.protobuf.Empty + 76, // 88: grpc.Bridge.CurrentEmailClient:input_type -> google.protobuf.Empty + 9, // 89: grpc.Bridge.ReportBug:input_type -> grpc.ReportBugRequest + 75, // 90: grpc.Bridge.ForceLauncher:input_type -> google.protobuf.StringValue + 75, // 91: grpc.Bridge.SetMainExecutable:input_type -> google.protobuf.StringValue + 10, // 92: grpc.Bridge.Login:input_type -> grpc.LoginRequest + 10, // 93: grpc.Bridge.Login2FA:input_type -> grpc.LoginRequest + 10, // 94: grpc.Bridge.Login2Passwords:input_type -> grpc.LoginRequest + 11, // 95: grpc.Bridge.LoginAbort:input_type -> grpc.LoginAbortRequest + 76, // 96: grpc.Bridge.CheckUpdate:input_type -> google.protobuf.Empty + 76, // 97: grpc.Bridge.InstallUpdate:input_type -> google.protobuf.Empty + 77, // 98: grpc.Bridge.SetIsAutomaticUpdateOn:input_type -> google.protobuf.BoolValue + 76, // 99: grpc.Bridge.IsAutomaticUpdateOn:input_type -> google.protobuf.Empty + 76, // 100: grpc.Bridge.DiskCachePath:input_type -> google.protobuf.Empty + 75, // 101: grpc.Bridge.SetDiskCachePath:input_type -> google.protobuf.StringValue + 77, // 102: grpc.Bridge.SetIsDoHEnabled:input_type -> google.protobuf.BoolValue + 76, // 103: grpc.Bridge.IsDoHEnabled:input_type -> google.protobuf.Empty + 76, // 104: grpc.Bridge.MailServerSettings:input_type -> google.protobuf.Empty + 12, // 105: grpc.Bridge.SetMailServerSettings:input_type -> grpc.ImapSmtpSettings + 76, // 106: grpc.Bridge.Hostname:input_type -> google.protobuf.Empty + 78, // 107: grpc.Bridge.IsPortFree:input_type -> google.protobuf.Int32Value + 76, // 108: grpc.Bridge.AvailableKeychains:input_type -> google.protobuf.Empty + 75, // 109: grpc.Bridge.SetCurrentKeychain:input_type -> google.protobuf.StringValue + 76, // 110: grpc.Bridge.CurrentKeychain:input_type -> google.protobuf.Empty + 76, // 111: grpc.Bridge.GetUserList:input_type -> google.protobuf.Empty + 75, // 112: grpc.Bridge.GetUser:input_type -> google.protobuf.StringValue + 15, // 113: grpc.Bridge.SetUserSplitMode:input_type -> grpc.UserSplitModeRequest + 16, // 114: grpc.Bridge.SendBadEventUserFeedback:input_type -> grpc.UserBadEventFeedbackRequest + 75, // 115: grpc.Bridge.LogoutUser:input_type -> google.protobuf.StringValue + 75, // 116: grpc.Bridge.RemoveUser:input_type -> google.protobuf.StringValue + 18, // 117: grpc.Bridge.ConfigureUserAppleMail:input_type -> grpc.ConfigureAppleMailRequest + 76, // 118: grpc.Bridge.ReportBugClicked:input_type -> google.protobuf.Empty + 75, // 119: grpc.Bridge.AutoconfigClicked:input_type -> google.protobuf.StringValue + 75, // 120: grpc.Bridge.KBArticleClicked:input_type -> google.protobuf.StringValue + 76, // 121: grpc.Bridge.IsTLSCertificateInstalled:input_type -> google.protobuf.Empty + 76, // 122: grpc.Bridge.InstallTLSCertificate:input_type -> google.protobuf.Empty + 75, // 123: grpc.Bridge.ExportTLSCertificates:input_type -> google.protobuf.StringValue + 19, // 124: grpc.Bridge.RunEventStream:input_type -> grpc.EventStreamRequest + 76, // 125: grpc.Bridge.StopEventStream:input_type -> google.protobuf.Empty + 75, // 126: grpc.Bridge.CheckTokens:output_type -> google.protobuf.StringValue + 76, // 127: grpc.Bridge.AddLogEntry:output_type -> google.protobuf.Empty + 8, // 128: grpc.Bridge.GuiReady:output_type -> grpc.GuiReadyResponse + 76, // 129: grpc.Bridge.Quit:output_type -> google.protobuf.Empty + 76, // 130: grpc.Bridge.Restart:output_type -> google.protobuf.Empty + 77, // 131: grpc.Bridge.ShowOnStartup:output_type -> google.protobuf.BoolValue + 76, // 132: grpc.Bridge.SetIsAutostartOn:output_type -> google.protobuf.Empty + 77, // 133: grpc.Bridge.IsAutostartOn:output_type -> google.protobuf.BoolValue + 76, // 134: grpc.Bridge.SetIsBetaEnabled:output_type -> google.protobuf.Empty + 77, // 135: grpc.Bridge.IsBetaEnabled:output_type -> google.protobuf.BoolValue + 76, // 136: grpc.Bridge.SetIsAllMailVisible:output_type -> google.protobuf.Empty + 77, // 137: grpc.Bridge.IsAllMailVisible:output_type -> google.protobuf.BoolValue + 76, // 138: grpc.Bridge.SetIsTelemetryDisabled:output_type -> google.protobuf.Empty + 77, // 139: grpc.Bridge.IsTelemetryDisabled:output_type -> google.protobuf.BoolValue + 75, // 140: grpc.Bridge.GoOs:output_type -> google.protobuf.StringValue + 76, // 141: grpc.Bridge.TriggerReset:output_type -> google.protobuf.Empty + 75, // 142: grpc.Bridge.Version:output_type -> google.protobuf.StringValue + 75, // 143: grpc.Bridge.LogsPath:output_type -> google.protobuf.StringValue + 75, // 144: grpc.Bridge.LicensePath:output_type -> google.protobuf.StringValue + 75, // 145: grpc.Bridge.ReleaseNotesPageLink:output_type -> google.protobuf.StringValue + 75, // 146: grpc.Bridge.DependencyLicensesLink:output_type -> google.protobuf.StringValue + 75, // 147: grpc.Bridge.LandingPageLink:output_type -> google.protobuf.StringValue + 76, // 148: grpc.Bridge.SetColorSchemeName:output_type -> google.protobuf.Empty + 75, // 149: grpc.Bridge.ColorSchemeName:output_type -> google.protobuf.StringValue + 75, // 150: grpc.Bridge.CurrentEmailClient:output_type -> google.protobuf.StringValue + 76, // 151: grpc.Bridge.ReportBug:output_type -> google.protobuf.Empty + 76, // 152: grpc.Bridge.ForceLauncher:output_type -> google.protobuf.Empty + 76, // 153: grpc.Bridge.SetMainExecutable:output_type -> google.protobuf.Empty + 76, // 154: grpc.Bridge.Login:output_type -> google.protobuf.Empty + 76, // 155: grpc.Bridge.Login2FA:output_type -> google.protobuf.Empty + 76, // 156: grpc.Bridge.Login2Passwords:output_type -> google.protobuf.Empty + 76, // 157: grpc.Bridge.LoginAbort:output_type -> google.protobuf.Empty + 76, // 158: grpc.Bridge.CheckUpdate:output_type -> google.protobuf.Empty + 76, // 159: grpc.Bridge.InstallUpdate:output_type -> google.protobuf.Empty + 76, // 160: grpc.Bridge.SetIsAutomaticUpdateOn:output_type -> google.protobuf.Empty + 77, // 161: grpc.Bridge.IsAutomaticUpdateOn:output_type -> google.protobuf.BoolValue + 75, // 162: grpc.Bridge.DiskCachePath:output_type -> google.protobuf.StringValue + 76, // 163: grpc.Bridge.SetDiskCachePath:output_type -> google.protobuf.Empty + 76, // 164: grpc.Bridge.SetIsDoHEnabled:output_type -> google.protobuf.Empty + 77, // 165: grpc.Bridge.IsDoHEnabled:output_type -> google.protobuf.BoolValue + 12, // 166: grpc.Bridge.MailServerSettings:output_type -> grpc.ImapSmtpSettings + 76, // 167: grpc.Bridge.SetMailServerSettings:output_type -> google.protobuf.Empty + 75, // 168: grpc.Bridge.Hostname:output_type -> google.protobuf.StringValue + 77, // 169: grpc.Bridge.IsPortFree:output_type -> google.protobuf.BoolValue + 13, // 170: grpc.Bridge.AvailableKeychains:output_type -> grpc.AvailableKeychainsResponse + 76, // 171: grpc.Bridge.SetCurrentKeychain:output_type -> google.protobuf.Empty + 75, // 172: grpc.Bridge.CurrentKeychain:output_type -> google.protobuf.StringValue + 17, // 173: grpc.Bridge.GetUserList:output_type -> grpc.UserListResponse + 14, // 174: grpc.Bridge.GetUser:output_type -> grpc.User + 76, // 175: grpc.Bridge.SetUserSplitMode:output_type -> google.protobuf.Empty + 76, // 176: grpc.Bridge.SendBadEventUserFeedback:output_type -> google.protobuf.Empty + 76, // 177: grpc.Bridge.LogoutUser:output_type -> google.protobuf.Empty + 76, // 178: grpc.Bridge.RemoveUser:output_type -> google.protobuf.Empty + 76, // 179: grpc.Bridge.ConfigureUserAppleMail:output_type -> google.protobuf.Empty + 76, // 180: grpc.Bridge.ReportBugClicked:output_type -> google.protobuf.Empty + 76, // 181: grpc.Bridge.AutoconfigClicked:output_type -> google.protobuf.Empty + 76, // 182: grpc.Bridge.KBArticleClicked:output_type -> google.protobuf.Empty + 77, // 183: grpc.Bridge.IsTLSCertificateInstalled:output_type -> google.protobuf.BoolValue + 76, // 184: grpc.Bridge.InstallTLSCertificate:output_type -> google.protobuf.Empty + 76, // 185: grpc.Bridge.ExportTLSCertificates:output_type -> google.protobuf.Empty + 20, // 186: grpc.Bridge.RunEventStream:output_type -> grpc.StreamEvent + 76, // 187: grpc.Bridge.StopEventStream:output_type -> google.protobuf.Empty + 126, // [126:188] is the sub-list for method output_type + 64, // [64:126] is the sub-list for method input_type + 64, // [64:64] is the sub-list for extension type_name + 64, // [64:64] is the sub-list for extension extendee + 0, // [0:64] is the sub-list for field type_name } func init() { file_bridge_proto_init() } @@ -5705,7 +5906,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LoginEvent); i { + switch v := v.(*CertificateInstallSuccessEvent); i { case 0: return &v.state case 1: @@ -5717,7 +5918,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LoginErrorEvent); i { + switch v := v.(*CertificateInstallCanceledEvent); i { case 0: return &v.state case 1: @@ -5729,7 +5930,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LoginTfaRequestedEvent); i { + switch v := v.(*CertificateInstallFailedEvent); i { case 0: return &v.state case 1: @@ -5741,7 +5942,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LoginTwoPasswordsRequestedEvent); i { + switch v := v.(*LoginEvent); i { case 0: return &v.state case 1: @@ -5753,7 +5954,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LoginFinishedEvent); i { + switch v := v.(*LoginErrorEvent); i { case 0: return &v.state case 1: @@ -5765,7 +5966,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateEvent); i { + switch v := v.(*LoginTfaRequestedEvent); i { case 0: return &v.state case 1: @@ -5777,7 +5978,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateErrorEvent); i { + switch v := v.(*LoginTwoPasswordsRequestedEvent); i { case 0: return &v.state case 1: @@ -5789,7 +5990,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateManualReadyEvent); i { + switch v := v.(*LoginFinishedEvent); i { case 0: return &v.state case 1: @@ -5801,7 +6002,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateManualRestartNeededEvent); i { + switch v := v.(*UpdateEvent); i { case 0: return &v.state case 1: @@ -5813,7 +6014,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateForceEvent); i { + switch v := v.(*UpdateErrorEvent); i { case 0: return &v.state case 1: @@ -5825,7 +6026,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateSilentRestartNeeded); i { + switch v := v.(*UpdateManualReadyEvent); i { case 0: return &v.state case 1: @@ -5837,7 +6038,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateIsLatestVersion); i { + switch v := v.(*UpdateManualRestartNeededEvent); i { case 0: return &v.state case 1: @@ -5849,7 +6050,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateCheckFinished); i { + switch v := v.(*UpdateForceEvent); i { case 0: return &v.state case 1: @@ -5861,7 +6062,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateVersionChanged); i { + switch v := v.(*UpdateSilentRestartNeeded); i { case 0: return &v.state case 1: @@ -5873,7 +6074,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DiskCacheEvent); i { + switch v := v.(*UpdateIsLatestVersion); i { case 0: return &v.state case 1: @@ -5885,7 +6086,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DiskCacheErrorEvent); i { + switch v := v.(*UpdateCheckFinished); i { case 0: return &v.state case 1: @@ -5897,7 +6098,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DiskCachePathChangedEvent); i { + switch v := v.(*UpdateVersionChanged); i { case 0: return &v.state case 1: @@ -5909,7 +6110,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DiskCachePathChangeFinishedEvent); i { + switch v := v.(*DiskCacheEvent); i { case 0: return &v.state case 1: @@ -5921,7 +6122,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MailServerSettingsEvent); i { + switch v := v.(*DiskCacheErrorEvent); i { case 0: return &v.state case 1: @@ -5933,7 +6134,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MailServerSettingsErrorEvent); i { + switch v := v.(*DiskCachePathChangedEvent); i { case 0: return &v.state case 1: @@ -5945,7 +6146,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MailServerSettingsChangedEvent); i { + switch v := v.(*DiskCachePathChangeFinishedEvent); i { case 0: return &v.state case 1: @@ -5957,7 +6158,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChangeMailServerSettingsFinishedEvent); i { + switch v := v.(*MailServerSettingsEvent); i { case 0: return &v.state case 1: @@ -5969,7 +6170,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*KeychainEvent); i { + switch v := v.(*MailServerSettingsErrorEvent); i { case 0: return &v.state case 1: @@ -5981,7 +6182,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[46].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChangeKeychainFinishedEvent); i { + switch v := v.(*MailServerSettingsChangedEvent); i { case 0: return &v.state case 1: @@ -5993,7 +6194,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[47].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HasNoKeychainEvent); i { + switch v := v.(*ChangeMailServerSettingsFinishedEvent); i { case 0: return &v.state case 1: @@ -6005,7 +6206,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[48].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RebuildKeychainEvent); i { + switch v := v.(*KeychainEvent); i { case 0: return &v.state case 1: @@ -6017,7 +6218,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MailEvent); i { + switch v := v.(*ChangeKeychainFinishedEvent); i { case 0: return &v.state case 1: @@ -6029,7 +6230,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NoActiveKeyForRecipientEvent); i { + switch v := v.(*HasNoKeychainEvent); i { case 0: return &v.state case 1: @@ -6041,7 +6242,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddressChangedEvent); i { + switch v := v.(*RebuildKeychainEvent); i { case 0: return &v.state case 1: @@ -6053,7 +6254,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[52].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddressChangedLogoutEvent); i { + switch v := v.(*MailEvent); i { case 0: return &v.state case 1: @@ -6065,7 +6266,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[53].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ApiCertIssueEvent); i { + switch v := v.(*NoActiveKeyForRecipientEvent); i { case 0: return &v.state case 1: @@ -6077,7 +6278,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[54].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UserEvent); i { + switch v := v.(*AddressChangedEvent); i { case 0: return &v.state case 1: @@ -6089,7 +6290,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[55].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ToggleSplitModeFinishedEvent); i { + switch v := v.(*AddressChangedLogoutEvent); i { case 0: return &v.state case 1: @@ -6101,7 +6302,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[56].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UserDisconnectedEvent); i { + switch v := v.(*ApiCertIssueEvent); i { case 0: return &v.state case 1: @@ -6113,7 +6314,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[57].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UserChangedEvent); i { + switch v := v.(*UserEvent); i { case 0: return &v.state case 1: @@ -6125,7 +6326,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[58].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UserBadEvent); i { + switch v := v.(*ToggleSplitModeFinishedEvent); i { case 0: return &v.state case 1: @@ -6137,7 +6338,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[59].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UsedBytesChangedEvent); i { + switch v := v.(*UserDisconnectedEvent); i { case 0: return &v.state case 1: @@ -6149,7 +6350,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[60].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ImapLoginFailedEvent); i { + switch v := v.(*UserChangedEvent); i { case 0: return &v.state case 1: @@ -6161,7 +6362,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[61].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SyncStartedEvent); i { + switch v := v.(*UserBadEvent); i { case 0: return &v.state case 1: @@ -6173,7 +6374,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[62].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SyncFinishedEvent); i { + switch v := v.(*UsedBytesChangedEvent); i { case 0: return &v.state case 1: @@ -6185,7 +6386,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[63].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SyncProgressEvent); i { + switch v := v.(*ImapLoginFailedEvent); i { case 0: return &v.state case 1: @@ -6197,6 +6398,42 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[64].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncStartedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_bridge_proto_msgTypes[65].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncFinishedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_bridge_proto_msgTypes[66].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncProgressEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_bridge_proto_msgTypes[67].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GenericErrorEvent); i { case 0: return &v.state @@ -6229,15 +6466,18 @@ func file_bridge_proto_init() { (*AppEvent_ReportBugError)(nil), (*AppEvent_ShowMainWindow)(nil), (*AppEvent_ReportBugFallback)(nil), + (*AppEvent_CertificateInstallSuccess)(nil), + (*AppEvent_CertificateInstallCanceled)(nil), + (*AppEvent_CertificateInstallFailed)(nil), } - file_bridge_proto_msgTypes[23].OneofWrappers = []interface{}{ + file_bridge_proto_msgTypes[26].OneofWrappers = []interface{}{ (*LoginEvent_Error)(nil), (*LoginEvent_TfaRequested)(nil), (*LoginEvent_TwoPasswordRequested)(nil), (*LoginEvent_Finished)(nil), (*LoginEvent_AlreadyLoggedIn)(nil), } - file_bridge_proto_msgTypes[28].OneofWrappers = []interface{}{ + file_bridge_proto_msgTypes[31].OneofWrappers = []interface{}{ (*UpdateEvent_Error)(nil), (*UpdateEvent_ManualReady)(nil), (*UpdateEvent_ManualRestartNeeded)(nil), @@ -6247,28 +6487,28 @@ func file_bridge_proto_init() { (*UpdateEvent_CheckFinished)(nil), (*UpdateEvent_VersionChanged)(nil), } - file_bridge_proto_msgTypes[37].OneofWrappers = []interface{}{ + file_bridge_proto_msgTypes[40].OneofWrappers = []interface{}{ (*DiskCacheEvent_Error)(nil), (*DiskCacheEvent_PathChanged)(nil), (*DiskCacheEvent_PathChangeFinished)(nil), } - file_bridge_proto_msgTypes[41].OneofWrappers = []interface{}{ + file_bridge_proto_msgTypes[44].OneofWrappers = []interface{}{ (*MailServerSettingsEvent_Error)(nil), (*MailServerSettingsEvent_MailServerSettingsChanged)(nil), (*MailServerSettingsEvent_ChangeMailServerSettingsFinished)(nil), } - file_bridge_proto_msgTypes[45].OneofWrappers = []interface{}{ + file_bridge_proto_msgTypes[48].OneofWrappers = []interface{}{ (*KeychainEvent_ChangeKeychainFinished)(nil), (*KeychainEvent_HasNoKeychain)(nil), (*KeychainEvent_RebuildKeychain)(nil), } - file_bridge_proto_msgTypes[49].OneofWrappers = []interface{}{ + file_bridge_proto_msgTypes[52].OneofWrappers = []interface{}{ (*MailEvent_NoActiveKeyForRecipientEvent)(nil), (*MailEvent_AddressChanged)(nil), (*MailEvent_AddressChangedLogout)(nil), (*MailEvent_ApiCertIssue)(nil), } - file_bridge_proto_msgTypes[54].OneofWrappers = []interface{}{ + file_bridge_proto_msgTypes[57].OneofWrappers = []interface{}{ (*UserEvent_ToggleSplitModeFinished)(nil), (*UserEvent_UserDisconnected)(nil), (*UserEvent_UserChanged)(nil), @@ -6285,7 +6525,7 @@ func file_bridge_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_bridge_proto_rawDesc, NumEnums: 7, - NumMessages: 65, + NumMessages: 68, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/frontend/grpc/bridge.proto b/internal/frontend/grpc/bridge.proto index 6781865e..23df91b5 100644 --- a/internal/frontend/grpc/bridge.proto +++ b/internal/frontend/grpc/bridge.proto @@ -56,7 +56,6 @@ service Bridge { rpc ColorSchemeName(google.protobuf.Empty) returns (google.protobuf.StringValue); // TODO Color scheme should probably entirely be managed by the client. rpc CurrentEmailClient(google.protobuf.Empty) returns (google.protobuf.StringValue); rpc ReportBug(ReportBugRequest) returns (google.protobuf.Empty); - rpc ExportTLSCertificates(google.protobuf.StringValue) returns (google.protobuf.Empty); rpc ForceLauncher(google.protobuf.StringValue) returns (google.protobuf.Empty); rpc SetMainExecutable(google.protobuf.StringValue) returns (google.protobuf.Empty); @@ -103,6 +102,11 @@ service Bridge { rpc AutoconfigClicked(google.protobuf.StringValue) returns (google.protobuf.Empty); rpc KBArticleClicked(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); + rpc ExportTLSCertificates(google.protobuf.StringValue) returns (google.protobuf.Empty); + // Server -> Client event stream rpc RunEventStream(EventStreamRequest) returns (stream StreamEvent); // Keep streaming until StopEventStream is called. rpc StopEventStream(google.protobuf.Empty) returns (google.protobuf.Empty); @@ -262,6 +266,9 @@ message AppEvent { ReportBugErrorEvent reportBugError = 6; ShowMainWindowEvent showMainWindow = 7; ReportBugFallbackEvent reportBugFallback = 8; + CertificateInstallSuccessEvent certificateInstallSuccess = 9; + CertificateInstallCanceledEvent certificateInstallCanceled = 10; + CertificateInstallFailedEvent certificateInstallFailed = 11; } } @@ -276,6 +283,9 @@ message ReportBugSuccessEvent {} message ReportBugErrorEvent {} message ShowMainWindowEvent {} message ReportBugFallbackEvent {} +message CertificateInstallSuccessEvent {} +message CertificateInstallCanceledEvent {} +message CertificateInstallFailedEvent {} //********************************************************** // Login related events diff --git a/internal/frontend/grpc/bridge_grpc.pb.go b/internal/frontend/grpc/bridge_grpc.pb.go index 4767c41a..73ef1590 100644 --- a/internal/frontend/grpc/bridge_grpc.pb.go +++ b/internal/frontend/grpc/bridge_grpc.pb.go @@ -51,7 +51,6 @@ type BridgeClient interface { ColorSchemeName(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error) CurrentEmailClient(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error) ReportBug(ctx context.Context, in *ReportBugRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) - ExportTLSCertificates(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) ForceLauncher(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) SetMainExecutable(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) // login @@ -90,6 +89,10 @@ type BridgeClient interface { 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) KBArticleClicked(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) + ExportTLSCertificates(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) // Server -> Client event stream RunEventStream(ctx context.Context, in *EventStreamRequest, opts ...grpc.CallOption) (Bridge_RunEventStreamClient, error) StopEventStream(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) @@ -337,15 +340,6 @@ func (c *bridgeClient) ReportBug(ctx context.Context, in *ReportBugRequest, opts return out, nil } -func (c *bridgeClient) ExportTLSCertificates(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) { - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, "/grpc.Bridge/ExportTLSCertificates", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *bridgeClient) ForceLauncher(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) { out := new(emptypb.Empty) err := c.cc.Invoke(ctx, "/grpc.Bridge/ForceLauncher", in, out, opts...) @@ -625,6 +619,33 @@ func (c *bridgeClient) KBArticleClicked(ctx context.Context, in *wrapperspb.Stri 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, "/grpc.Bridge/IsTLSCertificateInstalled", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *bridgeClient) InstallTLSCertificate(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/grpc.Bridge/InstallTLSCertificate", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *bridgeClient) ExportTLSCertificates(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/grpc.Bridge/ExportTLSCertificates", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *bridgeClient) RunEventStream(ctx context.Context, in *EventStreamRequest, opts ...grpc.CallOption) (Bridge_RunEventStreamClient, error) { stream, err := c.cc.NewStream(ctx, &Bridge_ServiceDesc.Streams[0], "/grpc.Bridge/RunEventStream", opts...) if err != nil { @@ -697,7 +718,6 @@ type BridgeServer interface { ColorSchemeName(context.Context, *emptypb.Empty) (*wrapperspb.StringValue, error) CurrentEmailClient(context.Context, *emptypb.Empty) (*wrapperspb.StringValue, error) ReportBug(context.Context, *ReportBugRequest) (*emptypb.Empty, error) - ExportTLSCertificates(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) ForceLauncher(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) SetMainExecutable(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) // login @@ -736,6 +756,10 @@ type BridgeServer interface { ReportBugClicked(context.Context, *emptypb.Empty) (*emptypb.Empty, error) AutoconfigClicked(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) KBArticleClicked(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) + ExportTLSCertificates(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) // Server -> Client event stream RunEventStream(*EventStreamRequest, Bridge_RunEventStreamServer) error StopEventStream(context.Context, *emptypb.Empty) (*emptypb.Empty, error) @@ -824,9 +848,6 @@ func (UnimplementedBridgeServer) CurrentEmailClient(context.Context, *emptypb.Em func (UnimplementedBridgeServer) ReportBug(context.Context, *ReportBugRequest) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method ReportBug not implemented") } -func (UnimplementedBridgeServer) ExportTLSCertificates(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method ExportTLSCertificates not implemented") -} func (UnimplementedBridgeServer) ForceLauncher(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method ForceLauncher not implemented") } @@ -920,6 +941,15 @@ func (UnimplementedBridgeServer) AutoconfigClicked(context.Context, *wrapperspb. func (UnimplementedBridgeServer) KBArticleClicked(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method KBArticleClicked not implemented") } +func (UnimplementedBridgeServer) IsTLSCertificateInstalled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) { + return nil, status.Errorf(codes.Unimplemented, "method IsTLSCertificateInstalled not implemented") +} +func (UnimplementedBridgeServer) InstallTLSCertificate(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method InstallTLSCertificate not implemented") +} +func (UnimplementedBridgeServer) ExportTLSCertificates(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method ExportTLSCertificates not implemented") +} func (UnimplementedBridgeServer) RunEventStream(*EventStreamRequest, Bridge_RunEventStreamServer) error { return status.Errorf(codes.Unimplemented, "method RunEventStream not implemented") } @@ -1407,24 +1437,6 @@ func _Bridge_ReportBug_Handler(srv interface{}, ctx context.Context, dec func(in return interceptor(ctx, in, info, handler) } -func _Bridge_ExportTLSCertificates_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).ExportTLSCertificates(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/grpc.Bridge/ExportTLSCertificates", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(BridgeServer).ExportTLSCertificates(ctx, req.(*wrapperspb.StringValue)) - } - return interceptor(ctx, in, info, handler) -} - func _Bridge_ForceLauncher_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 { @@ -1983,6 +1995,60 @@ func _Bridge_KBArticleClicked_Handler(srv interface{}, ctx context.Context, dec 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 { + return nil, err + } + if interceptor == nil { + return srv.(BridgeServer).IsTLSCertificateInstalled(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.Bridge/IsTLSCertificateInstalled", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BridgeServer).IsTLSCertificateInstalled(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Bridge_InstallTLSCertificate_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).InstallTLSCertificate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.Bridge/InstallTLSCertificate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BridgeServer).InstallTLSCertificate(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Bridge_ExportTLSCertificates_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).ExportTLSCertificates(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.Bridge/ExportTLSCertificates", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BridgeServer).ExportTLSCertificates(ctx, req.(*wrapperspb.StringValue)) + } + return interceptor(ctx, in, info, handler) +} + func _Bridge_RunEventStream_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(EventStreamRequest) if err := stream.RecvMsg(m); err != nil { @@ -2133,10 +2199,6 @@ var Bridge_ServiceDesc = grpc.ServiceDesc{ MethodName: "ReportBug", Handler: _Bridge_ReportBug_Handler, }, - { - MethodName: "ExportTLSCertificates", - Handler: _Bridge_ExportTLSCertificates_Handler, - }, { MethodName: "ForceLauncher", Handler: _Bridge_ForceLauncher_Handler, @@ -2261,6 +2323,18 @@ var Bridge_ServiceDesc = grpc.ServiceDesc{ MethodName: "KBArticleClicked", Handler: _Bridge_KBArticleClicked_Handler, }, + { + MethodName: "IsTLSCertificateInstalled", + Handler: _Bridge_IsTLSCertificateInstalled_Handler, + }, + { + MethodName: "InstallTLSCertificate", + Handler: _Bridge_InstallTLSCertificate_Handler, + }, + { + MethodName: "ExportTLSCertificates", + Handler: _Bridge_ExportTLSCertificates_Handler, + }, { MethodName: "StopEventStream", Handler: _Bridge_StopEventStream_Handler, diff --git a/internal/frontend/grpc/event_factory.go b/internal/frontend/grpc/event_factory.go index 66e79ebe..13310887 100644 --- a/internal/frontend/grpc/event_factory.go +++ b/internal/frontend/grpc/event_factory.go @@ -45,6 +45,18 @@ func NewReportBugFallbackEvent() *StreamEvent { return appEvent(&AppEvent{Event: &AppEvent_ReportBugFallback{ReportBugFallback: &ReportBugFallbackEvent{}}}) } +func NewCertInstallSuccessEvent() *StreamEvent { + return appEvent(&AppEvent{Event: &AppEvent_CertificateInstallSuccess{CertificateInstallSuccess: &CertificateInstallSuccessEvent{}}}) +} + +func NewCertInstallCanceledEvent() *StreamEvent { + return appEvent(&AppEvent{Event: &AppEvent_CertificateInstallCanceled{CertificateInstallCanceled: &CertificateInstallCanceledEvent{}}}) +} + +func NewCertInstallFailedEvent() *StreamEvent { + return appEvent(&AppEvent{Event: &AppEvent_CertificateInstallFailed{CertificateInstallFailed: &CertificateInstallFailedEvent{}}}) +} + func NewShowMainWindowEvent() *StreamEvent { return appEvent(&AppEvent{Event: &AppEvent_ShowMainWindow{ShowMainWindow: &ShowMainWindowEvent{}}}) } diff --git a/internal/frontend/grpc/service_cert.go b/internal/frontend/grpc/service_cert.go new file mode 100644 index 00000000..399f3dc7 --- /dev/null +++ b/internal/frontend/grpc/service_cert.go @@ -0,0 +1,79 @@ +// Copyright (c) 2023 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 . + +package grpc + +import ( + "context" + "errors" + "os" + "path/filepath" + + "github.com/ProtonMail/gluon/async" + "github.com/ProtonMail/proton-bridge/v3/internal/certs" + "google.golang.org/protobuf/types/known/emptypb" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +func (s *Service) IsTLSCertificateInstalled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) { + s.log.Info("IsTLSCertificateInstalled") + + cert, _ := s.bridge.GetBridgeTLSCert() + + return &wrapperspb.BoolValue{Value: certs.NewInstaller().IsCertInstalled(cert)}, nil +} + +func (s *Service) InstallTLSCertificate(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + s.log.Info("InstallTLSCertificate") + + go func() { + defer async.HandlePanic(s.panicHandler) + cert, _ := s.bridge.GetBridgeTLSCert() + + err := certs.NewInstaller().InstallCert(cert) + switch { + case err == nil: + _ = s.SendEvent(NewCertInstallSuccessEvent()) + case errors.Is(err, certs.ErrUserCanceledCertificateInstall): + _ = s.SendEvent(NewCertInstallCanceledEvent()) + default: + _ = s.SendEvent(NewCertInstallFailedEvent()) + } + }() + + return &emptypb.Empty{}, nil +} + +func (s *Service) ExportTLSCertificates(_ context.Context, folderPath *wrapperspb.StringValue) (*emptypb.Empty, error) { + s.log.WithField("folderPath", folderPath).Info("ExportTLSCertificates") + + go func() { + defer async.HandlePanic(s.panicHandler) + + cert, key := s.bridge.GetBridgeTLSCert() + + if err := os.WriteFile(filepath.Join(folderPath.Value, "cert.pem"), cert, 0o600); err != nil { + _ = s.SendEvent(NewGenericErrorEvent(ErrorCode_TLS_CERT_EXPORT_ERROR)) + } + + if err := os.WriteFile(filepath.Join(folderPath.Value, "key.pem"), key, 0o600); err != nil { + _ = s.SendEvent(NewGenericErrorEvent(ErrorCode_TLS_KEY_EXPORT_ERROR)) + } + }() + + return &emptypb.Empty{}, nil +} diff --git a/internal/frontend/grpc/service_methods.go b/internal/frontend/grpc/service_methods.go index ae1d76f7..2f8189a0 100644 --- a/internal/frontend/grpc/service_methods.go +++ b/internal/frontend/grpc/service_methods.go @@ -21,8 +21,6 @@ import ( "context" "encoding/base64" "errors" - "os" - "path/filepath" "runtime" "github.com/Masterminds/semver/v3" @@ -364,26 +362,6 @@ func (s *Service) ReportBug(_ context.Context, report *ReportBugRequest) (*empty return &emptypb.Empty{}, nil } -func (s *Service) ExportTLSCertificates(_ context.Context, folderPath *wrapperspb.StringValue) (*emptypb.Empty, error) { - s.log.WithField("folderPath", folderPath).Info("ExportTLSCertificates") - - go func() { - defer async.HandlePanic(s.panicHandler) - - cert, key := s.bridge.GetBridgeTLSCert() - - if err := os.WriteFile(filepath.Join(folderPath.Value, "cert.pem"), cert, 0o600); err != nil { - _ = s.SendEvent(NewGenericErrorEvent(ErrorCode_TLS_CERT_EXPORT_ERROR)) - } - - if err := os.WriteFile(filepath.Join(folderPath.Value, "key.pem"), key, 0o600); err != nil { - _ = s.SendEvent(NewGenericErrorEvent(ErrorCode_TLS_KEY_EXPORT_ERROR)) - } - }() - - return &emptypb.Empty{}, nil -} - func (s *Service) ForceLauncher(_ context.Context, launcher *wrapperspb.StringValue) (*emptypb.Empty, error) { s.log.WithField("launcher", launcher.Value).Debug("ForceLauncher") From 69190daf3fc848d2e886f8b5d8c521b7b824a68a Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Thu, 17 Aug 2023 17:35:50 +0200 Subject: [PATCH 32/93] feat(GODT-2771): macOS cert install support in bridge-gui-test + placeholder QML. --- .../bridge-gui-tester/GRPCQtProxy.cpp | 8 + .../bridge-gui-tester/GRPCQtProxy.h | 2 + .../bridge-gui-tester/GRPCService.cpp | 70 ++++++--- .../bridge-gui-tester/GRPCService.h | 4 +- .../bridge-gui-tester/Tabs/SettingsTab.cpp | 34 ++++- .../bridge-gui-tester/Tabs/SettingsTab.h | 10 ++ .../bridge-gui-tester/Tabs/SettingsTab.ui | 65 ++++++-- .../bridge-gui/bridge-gui/QMLBackend.cpp | 19 ++- .../bridge-gui/bridge-gui/QMLBackend.h | 2 +- .../bridge-gui/bridge-gui/Resources.qrc | 1 + .../qml/SetupWizard/ClientConfigAppleMail.qml | 143 ++++++++++++++++++ .../qml/SetupWizard/ClientConfigSelector.qml | 1 + .../qml/SetupWizard/SetupWizard.qml | 16 +- .../bridgepp/bridgepp/GRPC/GRPCClient.cpp | 6 +- .../bridgepp/bridgepp/GRPC/GRPCClient.h | 2 +- 15 files changed, 338 insertions(+), 45 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.cpp index 205e0bbd..d7c0eb7c 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.cpp +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.cpp @@ -42,6 +42,7 @@ void GRPCQtProxy::connectSignals() { connect(this, &GRPCQtProxy::setIsTelemetryDisabledReceived, &settingsTab, &SettingsTab::setIsTelemetryDisabled); connect(this, &GRPCQtProxy::setColorSchemeNameReceived, &settingsTab, &SettingsTab::setColorSchemeName); connect(this, &GRPCQtProxy::reportBugReceived, &settingsTab, &SettingsTab::setBugReport); + connect(this, &GRPCQtProxy::installTLSCertificateReceived, &settingsTab, &SettingsTab::installTLSCertificate); connect(this, &GRPCQtProxy::exportTLSCertificatesReceived, &settingsTab, &SettingsTab::exportTLSCertificates); connect(this, &GRPCQtProxy::setIsStreamingReceived, &settingsTab, &SettingsTab::setIsStreaming); connect(this, &GRPCQtProxy::setClientPlatformReceived, &settingsTab, &SettingsTab::setClientPlatform); @@ -119,6 +120,13 @@ void GRPCQtProxy::reportBug(QString const &osType, QString const &osVersion, QSt } +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void GRPCQtProxy::installTLSCertificate() { + emit installTLSCertificateReceived(); +} + //**************************************************************************************************************************************************** /// \param[in] folderPath The folder path. //**************************************************************************************************************************************************** diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.h b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.h index 2f799eae..9d3ad154 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.h +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.h @@ -45,6 +45,7 @@ public: // member functions. void setColorSchemeName(QString const &name); ///< Forward a SetColorSchemeName call via a Qt Signal void reportBug(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address, QString const &description, bool includeLogs); ///< Forwards a ReportBug call via a Qt signal. + void installTLSCertificate(); ///< Forwards a InstallTLScertificate call via a Qt signal. void exportTLSCertificates(QString const &folderPath); //< Forward an 'ExportTLSCertificates' call via a Qt signal. void setIsStreaming(bool isStreaming); ///< Forward a isStreaming internal messages via a Qt signal. void setClientPlatform(QString const &clientPlatform); ///< Forward a setClientPlatform call via a Qt signal. @@ -67,6 +68,7 @@ signals: void setColorSchemeNameReceived(QString const &name); ///< Forward a SetColorScheme call via a Qt Signal void reportBugReceived(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address, QString const &description, bool includeLogs); ///< Signal for the ReportBug gRPC call + void installTLSCertificateReceived(); ///< Signal for the InstallTLSCertificate gRPC call. void exportTLSCertificatesReceived(QString const &folderPath); ///< Signal for the ExportTLSCertificates gRPC call. void setIsStreamingReceived(bool isStreaming); ///< Signal for the IsStreaming internal message. void setClientPlatformReceived(QString const &clientPlatform); ///< Signal for the SetClientPlatform gRPC call. diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp index b1a1b3c4..0b75e2b8 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp @@ -371,22 +371,6 @@ Status GRPCService::ReportBug(ServerContext *, ReportBugRequest const *request, } -//**************************************************************************************************************************************************** -/// \param[in] request The request -//**************************************************************************************************************************************************** -Status GRPCService::ExportTLSCertificates(ServerContext *, StringValue const *request, Empty *response) { - SettingsTab &tab = app().mainWindow().settingsTab(); - if (!tab.nextTLSCertExportWillSucceed()) { - qtProxy_.sendDelayedEvent(newGenericErrorEvent(grpc::TLS_CERT_EXPORT_ERROR)); - } - if (!tab.nextTLSKeyExportWillSucceed()) { - qtProxy_.sendDelayedEvent(newGenericErrorEvent(grpc::TLS_KEY_EXPORT_ERROR)); - } - qtProxy_.exportTLSCertificates(QString::fromStdString(request->value())); - return Status::OK; -} - - //**************************************************************************************************************************************************** /// \param[in] request The request. /// \return The status for the call. @@ -768,9 +752,60 @@ Status GRPCService::ConfigureUserAppleMail(ServerContext *, ConfigureAppleMailRe //**************************************************************************************************************************************************** /// \param[in] request The request -/// \param[in] writer The writer /// \return The status for the call. //**************************************************************************************************************************************************** +Status GRPCService::ExportTLSCertificates(ServerContext *, StringValue const *request, Empty *response) { + app().log().debug(__FUNCTION__); + SettingsTab &tab = app().mainWindow().settingsTab(); + if (!tab.nextTLSCertExportWillSucceed()) { + qtProxy_.sendDelayedEvent(newGenericErrorEvent(grpc::TLS_CERT_EXPORT_ERROR)); + } + if (!tab.nextTLSKeyExportWillSucceed()) { + qtProxy_.sendDelayedEvent(newGenericErrorEvent(grpc::TLS_KEY_EXPORT_ERROR)); + } + qtProxy_.exportTLSCertificates(QString::fromStdString(request->value())); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] response The reponse. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::IsTLSCertificateInstalled(ServerContext *, const Empty *request, BoolValue *response) { + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().isTLSCertificateInstalled()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +Status GRPCService::InstallTLSCertificate(ServerContext *, Empty const *, Empty *) { + app().log().debug(__FUNCTION__); + SPStreamEvent event; + qtProxy_.installTLSCertificate(); + switch (app().mainWindow().settingsTab().nextTLSCertIntallResult()) { + case SettingsTab::TLSCertInstallResult::Success: + event = newCertificateInstallSuccessEvent(); + break; + case SettingsTab::TLSCertInstallResult::Canceled: + event = newCertificateInstallCanceledEvent(); + break; + default: + event = newCertificateInstallCanceledEvent(); + break; + } + qtProxy_.sendDelayedEvent(event); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request +/// \param[in] writer The writer +//**************************************************************************************************************************************************** Status GRPCService::RunEventStream(ServerContext *ctx, EventStreamRequest const *request, ServerWriter *writer) { app().log().debug(__FUNCTION__); { @@ -860,4 +895,3 @@ void GRPCService::finishLogin() { qtProxy_.sendDelayedEvent(newLoginFinishedEvent(user->id(), alreadyExist)); } - diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h index c4d5b9e9..224108c5 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h @@ -65,7 +65,6 @@ public: // member functions. grpc::Status ColorSchemeName(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override; grpc::Status CurrentEmailClient(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override; grpc::Status ReportBug(::grpc::ServerContext *, ::grpc::ReportBugRequest const *request, ::google::protobuf::Empty *) override; - grpc::Status ExportTLSCertificates(::grpc::ServerContext *context, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *response) override; grpc::Status ForceLauncher(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override; grpc::Status SetMainExecutable(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override; grpc::Status Login(::grpc::ServerContext *, ::grpc::LoginRequest const *request, ::google::protobuf::Empty *) override; @@ -94,6 +93,9 @@ public: // member functions. grpc::Status LogoutUser(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override; grpc::Status RemoveUser(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override; grpc::Status ConfigureUserAppleMail(::grpc::ServerContext *, ::grpc::ConfigureAppleMailRequest const *request, ::google::protobuf::Empty *) override; + grpc::Status IsTLSCertificateInstalled(::grpc::ServerContext *, ::google::protobuf::Empty const*, ::google::protobuf::BoolValue *response) override; + grpc::Status InstallTLSCertificate(::grpc::ServerContext *, ::google::protobuf::Empty const*, ::google::protobuf::Empty *) override; + grpc::Status ExportTLSCertificates(::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. diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.cpp index a3d56a5a..4e839225 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.cpp +++ b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.cpp @@ -285,11 +285,20 @@ void SettingsTab::setBugReport(QString const &osType, QString const &osVersion, } +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void SettingsTab::installTLSCertificate() { + ui_.labelLastTLSCertInstall->setText(QString("Last install: %1").arg(QDateTime::currentDateTime().toString(Qt::ISODateWithMs))); + ui_.checkTLSCertIsInstalled->setChecked(this->nextTLSCertIntallResult() == TLSCertInstallResult::Success); +} + + //**************************************************************************************************************************************************** /// \param[in] folderPath The folder path. //**************************************************************************************************************************************************** void SettingsTab::exportTLSCertificates(QString const &folderPath) { - ui_.labeLastTLSCertsExport->setText(QString("%1 Export to %2") + ui_.labeLastTLSCertExport->setText(QString("%1 Export to %2") .arg(QDateTime::currentDateTime().toString(Qt::ISODateWithMs)) .arg(folderPath)); } @@ -303,6 +312,22 @@ bool SettingsTab::nextBugReportWillSucceed() const { } +//**************************************************************************************************************************************************** +/// \return the state of the 'TLS Certificate is installed' check box. +//**************************************************************************************************************************************************** +bool SettingsTab::isTLSCertificateInstalled() const { + return ui_.checkTLSCertIsInstalled->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \return The value for the 'Next TLS cert install result'. +//**************************************************************************************************************************************************** +SettingsTab::TLSCertInstallResult SettingsTab::nextTLSCertIntallResult() const { + return TLSCertInstallResult(ui_.comboNextTLSCertInstallResult->currentIndex()); +} + + //**************************************************************************************************************************************************** /// \return true if the 'Next TLS key export will succeed' check box is checked //**************************************************************************************************************************************************** @@ -505,4 +530,11 @@ void SettingsTab::resetUI() { ui_.comboCacheError->setCurrentIndex(0); ui_.checkAutomaticUpdate->setChecked(true); + + ui_.checkTLSCertIsInstalled->setChecked(false); + ui_.comboNextTLSCertInstallResult->setCurrentIndex(0); + ui_.checkTLSCertExportWillSucceed->setChecked(true); + ui_.checkTLSKeyExportWillSucceed->setChecked(true); + ui_.labeLastTLSCertExport->setText("Last export: never"); + ui_.labelLastTLSCertInstall->setText("Last install: never"); } diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.h b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.h index 2d082fc8..8412082d 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.h +++ b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.h @@ -28,6 +28,13 @@ //**************************************************************************************************************************************************** class SettingsTab : public QWidget { Q_OBJECT +public: // data types. + enum class TLSCertInstallResult { + Success = 0, + Canceled = 1, + Failure = 2 + }; ///< Enumberation for the result of a TLS certificate installation. + public: // member functions. explicit SettingsTab(QWidget *parent = nullptr); ///< Default constructor. SettingsTab(SettingsTab const &) = delete; ///< Disabled copy-constructor. @@ -54,6 +61,8 @@ public: // member functions. QString dependencyLicenseLink() const; ///< Get the content of the 'Dependency License Link' edit. QString landingPageLink() const; ///< Get the content of the 'Landing Page Link' edit. bool nextBugReportWillSucceed() const; ///< Get the status of the 'Next Bug Report Will Fail' check box. + bool isTLSCertificateInstalled() const; ///< Get the status of the 'TLS Certificate is installed' check box. + TLSCertInstallResult nextTLSCertIntallResult() const; ///< Get the value of the 'Next TLS Certificate install result' combo box. bool nextTLSCertExportWillSucceed() const; ///< Get the status of the 'Next TLS Cert export will succeed' check box. bool nextTLSKeyExportWillSucceed() const; ///< Get the status of the 'Next TLS Key export will succeed' check box. QString hostname() const; ///< Get the value of the 'Hostname' edit. @@ -79,6 +88,7 @@ public slots: void setColorSchemeName(QString const &name); ///< Set the value for the 'Use Dark Theme' check box. void setBugReport(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address, QString const &description, bool includeLogs); ///< Set the content of the bug report box. + void installTLSCertificate(); ///< Install the TLS certificate. void exportTLSCertificates(QString const &folderPath); ///< Export the TLS certificates. void setMailServerSettings(qint32 imapPort, qint32 smtpPort, bool useSSLForIMAP, bool useSSLForSMTP); ///< Change the mail server settings. void setIsDoHEnabled(bool enabled); ///< Set the value for the 'DoH Enabled' check box. diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.ui b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.ui index 7fb5b74e..964e0ce3 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.ui +++ b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.ui @@ -370,7 +370,7 @@ - + 0 @@ -380,34 +380,81 @@ TLS Certficates - - - + + + - Last Export: Never + Certificate is installed + + + false - + - TLS certificate export will succeed + Certificate export will succeed true - + - TLS private key export will succeed + Key export will succeed true + + + + Last Export: never + + + + + + + Last install: never + + + + + + + + + Next install will + + + + + + + + Succeed + + + + + Be Canceled + + + + + Fail + + + + + + diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp index de9c189f..3bdd3012 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp @@ -290,16 +290,6 @@ bool QMLBackend::isTLSCertificateInstalled() { } -//**************************************************************************************************************************************************** -// -//**************************************************************************************************************************************************** -void QMLBackend::installTLSCertificate() { - HANDLE_EXCEPTION( - app().grpc().installTLSCertificate(); - ) -} - - //**************************************************************************************************************************************************** /// \return The value for the 'showOnStartup' property. //**************************************************************************************************************************************************** @@ -963,6 +953,15 @@ void QMLBackend::reportBug(QString const &category, QString const &description, } +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void QMLBackend::installTLSCertificate() { + HANDLE_EXCEPTION( + app().grpc().installTLSCertificate(); + ) +} + //**************************************************************************************************************************************************** // //**************************************************************************************************************************************************** diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h index c8b80df8..159e5388 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h @@ -65,7 +65,6 @@ public: // member functions. Q_INVOKABLE QString collectAnswers(quint8 categoryId) const; ///< Collect answer for a given set of questions. Q_INVOKABLE void clearAnswers(); ///< Clear all collected answers. Q_INVOKABLE bool isTLSCertificateInstalled(); ///< Check if the bridge certificate is installed in the OS keychain. - Q_INVOKABLE void installTLSCertificate(); ///< Installs the Bridge TLS certificate in the Keychain. public: // Qt/QML properties. Note that the NOTIFY-er signal is required even for read-only properties (QML warning otherwise) Q_PROPERTY(bool showOnStartup READ showOnStartup NOTIFY showOnStartupChanged) @@ -197,6 +196,7 @@ public slots: // slot for signals received from QML -> To be forwarded to Bridge void installUpdate() const; ///< Slot for the update install. void triggerReset() const; ///< Slot for the triggering of reset. void reportBug(QString const &category, QString const &description, QString const &address, QString const &emailClient, bool includeLogs) const; ///< Slot for the bug report. + void installTLSCertificate(); ///< Installs the Bridge TLS certificate in the Keychain. void exportTLSCertificates() const; ///< Slot for the export of the TLS certificates. void onResetFinished(); ///< Slot for the reset finish signal. void onVersionChanged(); ///< Slot for the version change signal. diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index a1460606..9d2dcaf0 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -108,6 +108,7 @@ qml/SettingsView.qml qml/SetupWizard/ClientListItem.qml qml/SetupWizard/LeftPane.qml + qml/SetupWizard/ClientConfigAppleMail.qml qml/SetupWizard/ClientConfigOutlookSelector.qml qml/SetupWizard/ClientConfigParameters.qml qml/SetupWizard/ClientConfigSelector.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml new file mode 100644 index 00000000..098bf705 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml @@ -0,0 +1,143 @@ +// Copyright (c) 2023 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 . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.impl +import Proton + +Item { + id: root + enum Screen { + CertificateInstall = 0, + ProfileInstall = 1 + } + + property var wizard + + function showAutoConfig() { + certInstallButton.loading = false; + if (Backend.isTLSCertificateInstalled()) { + stack.currentIndex = ClientConfigAppleMail.Screen.ProfileInstall; + } else { + stack.currentIndex = ClientConfigAppleMail.Screen.CertificateInstall; + } + } + + StackLayout { + id: stack + anchors.fill: parent + + // stack index 0 + ColumnLayout { + id: certificateInstall + Layout.fillHeight: true + Layout.fillWidth: true + + Connections { + function onCertificateInstallCanceled() { + // Note: this will lead to an error message in the final version. + certInstallButton.loading = false; + console.error("Certificate installation was canceled"); + } + function onCertificateInstallFailed() { + // Note: this will lead to an error page later. + certInstallButton.loading = false; + console.error("Certificate installation failed"); + } + function onCertificateInstallSuccess() { + certInstallButton.loading = false; + console.error("Certification installed successfully"); + stack.currentIndex = ClientConfigAppleMail.Screen.ProfileInstall; + } + + target: Backend + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: "Certificate install placeholder" + type: Label.LabelType.Heading + wrapMode: Text.WordWrap + } + Button { + id: certInstallButton + Layout.fillWidth: true + Layout.topMargin: 48 + colorScheme: wizard.colorScheme + enabled: !loading + loading: false + text: "Install Certificate Placeholder" + + onClicked: { + certInstallButton.loading = true; + Backend.installTLSCertificate(); + } + } + Button { + Layout.fillWidth: true + Layout.topMargin: 32 + colorScheme: wizard.colorScheme + enabled: !certInstallButton.loading + secondary: true + text: qsTr("Cancel") + + onClicked: { + wizard.closeWizard(); + } + } + } + + // stack index 1 + ColumnLayout { + id: profileInstall + Layout.fillHeight: true + Layout.fillWidth: true + + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: "Profile install placeholder" + type: Label.LabelType.Heading + wrapMode: Text.WordWrap + } + Button { + Layout.fillWidth: true + Layout.topMargin: 48 + colorScheme: wizard.colorScheme + text: "Install Profile Placeholder" + + onClicked: { + wizard.user.configureAppleMail(wizard.address); + wizard.closeWizard(); + } + } + Button { + Layout.fillWidth: true + Layout.topMargin: 32 + colorScheme: wizard.colorScheme + secondary: true + text: qsTr("Cancel") + + onClicked: { + wizard.closeWizard(); + } + } + } + } +} \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml index e4058435..1d1fd0ef 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml @@ -49,6 +49,7 @@ Item { onClicked: { wizard.client = SetupWizard.Client.AppleMail; + wizard.showAppleMailAutoConfig(); } } ClientListItem { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index a85181b5..38083975 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -31,7 +31,8 @@ Item { Login, ClientConfigSelector, ClientConfigOutlookSelector, - ClientConfigWarning + ClientConfigWarning, + ClientConfigAppleMail } enum RootStack { TwoPanesView, @@ -80,6 +81,12 @@ Item { function closeWizard() { wizardEnded(); } + function showAppleMailAutoConfig() { + rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; + leftContent.showClientSelector(); + rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigAppleMail; + clientConfigAppleMail.showAutoConfig(); + } function showClientConfig(user, address) { root.user = user; root.address = address; @@ -222,6 +229,13 @@ Item { Layout.fillWidth: true wizard: root } + // rightContent stack index 5 + ClientConfigAppleMail { + id: clientConfigAppleMail + Layout.fillHeight: true + Layout.fillWidth: true + wizard: root + } } LinkLabel { id: reportProblemLink diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp index a43b4bcc..82cc382a 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp @@ -804,11 +804,11 @@ grpc::Status GRPCClient::setCurrentKeychain(QString const &keychain) { //**************************************************************************************************************************************************** -/// \param[out] isInstalled is The Bridge certificate installed in the keychain. +/// \param[out] outIsInstalled is The Bridge certificate installed in the keychain. /// \return The status for the call //**************************************************************************************************************************************************** -grpc::Status GRPCClient::isTLSCertificateInstalled(bool isInstalled) { - return this->logGRPCCallStatus(this->getBool(&Bridge::Stub::IsTLSCertificateInstalled, isInstalled), __FUNCTION__); +grpc::Status GRPCClient::isTLSCertificateInstalled(bool &outIsInstalled) { + return this->logGRPCCallStatus(this->getBool(&Bridge::Stub::IsTLSCertificateInstalled, outIsInstalled), __FUNCTION__); } diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h index d2977a57..1dbf2624 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h @@ -204,7 +204,7 @@ public: // keychain related calls grpc::Status setCurrentKeychain(QString const &keychain); public: // cert related calls - grpc::Status isTLSCertificateInstalled(bool isInstalled); ///< Perform the 'IsTLSCertificateInstalled' gRPC call. + grpc::Status isTLSCertificateInstalled(bool &outIsInstalled); ///< Perform the 'IsTLSCertificateInstalled' gRPC call. grpc::Status installTLSCertificate(); ///< Perform the 'InstallTLSCertificate' gRPC call. grpc::Status exportTLSCertificates(QString const &folderPath); ///< Performs the 'ExportTLSCertificates' gRPC call. From 452d3068f032634a64b53e976c4f552a70eb7495 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Thu, 17 Aug 2023 18:06:23 +0200 Subject: [PATCH 33/93] feat(GODT-2771): removed cert check and install on app startup on macOS. --- internal/app/vault.go | 18 ------------------ internal/certs/cert_store_darwin.go | 4 ---- internal/certs/cert_store_darwin_test.go | 4 ++-- internal/certs/cert_store_linux.go | 2 +- internal/certs/cert_store_windows.go | 2 +- internal/certs/installer.go | 6 ++++++ internal/vault/certs.go | 10 ---------- internal/vault/certs_test.go | 9 --------- internal/vault/types_certs.go | 3 +-- 9 files changed, 11 insertions(+), 47 deletions(-) diff --git a/internal/app/vault.go b/internal/app/vault.go index 042e89f2..dc349c22 100644 --- a/internal/app/vault.go +++ b/internal/app/vault.go @@ -22,7 +22,6 @@ import ( "path" "github.com/ProtonMail/gluon/async" - "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/vault" @@ -45,23 +44,6 @@ func WithVault(locations *locations.Locations, panicHandler async.PanicHandler, "corrupt": corrupt, }).Debug("Vault created") - // Install the certificates if needed. - if installed := encVault.GetCertsInstalled(); !installed { - logrus.Debug("Installing certificates") - - certPEM, _ := encVault.GetBridgeTLSCert() - - if err := certs.NewInstaller().InstallCert(certPEM); err != nil { - return fmt.Errorf("failed to install certs: %w", err) - } - - if err := encVault.SetCertsInstalled(true); err != nil { - return fmt.Errorf("failed to set certs installed: %w", err) - } - - logrus.Debug("Certificates successfully installed") - } - // GODT-1950: Add teardown actions (e.g. to close the vault). return fn(encVault, insecure, corrupt) diff --git a/internal/certs/cert_store_darwin.go b/internal/certs/cert_store_darwin.go index faafb448..c6642645 100644 --- a/internal/certs/cert_store_darwin.go +++ b/internal/certs/cert_store_darwin.go @@ -234,10 +234,6 @@ const ( errAuthorizationCanceled = -60006 ) -var ( - ErrUserCanceledCertificateInstall = errors.New("the user cancelled the authorization dialog") -) - // certPEMToDER converts a certificate in PEM format to DER format, which is the format required by Apple's Security framework. func certPEMToDER(certPEM []byte) ([]byte, error) { block, left := pem.Decode(certPEM) diff --git a/internal/certs/cert_store_darwin_test.go b/internal/certs/cert_store_darwin_test.go index 2b7dda59..2f6fa84f 100644 --- a/internal/certs/cert_store_darwin_test.go +++ b/internal/certs/cert_store_darwin_test.go @@ -40,7 +40,7 @@ func TestCertInKeychain(t *testing.T) { } // This test require human interaction (macOS security prompts), and is disabled by default. -func TestCertificateTrust(t *testing.T) { +func _TestCertificateTrust(t *testing.T) { certPEM := generatePEMCertificate(t) require.False(t, isCertTrusted(certPEM)) require.NoError(t, addCertToKeychain(certPEM)) @@ -52,7 +52,7 @@ func TestCertificateTrust(t *testing.T) { } // This test require human interaction (macOS security prompts), and is disabled by default. -func TestInstallAndRemove(t *testing.T) { +func _TestInstallAndRemove(t *testing.T) { certPEM := generatePEMCertificate(t) // fresh install diff --git a/internal/certs/cert_store_linux.go b/internal/certs/cert_store_linux.go index 16c4ff3f..072816ee 100644 --- a/internal/certs/cert_store_linux.go +++ b/internal/certs/cert_store_linux.go @@ -25,6 +25,6 @@ func uninstallCert([]byte) error { return nil // Linux doesn't have a root cert store. } -func isCertInstalled([]byte) error { +func isCertInstalled([]byte) bool { return false } diff --git a/internal/certs/cert_store_windows.go b/internal/certs/cert_store_windows.go index cb6f19e3..fd647f5a 100644 --- a/internal/certs/cert_store_windows.go +++ b/internal/certs/cert_store_windows.go @@ -25,6 +25,6 @@ func uninstallCert([]byte) error { return nil // NOTE(GODT-986): Uninstall certs from root cert store? } -func isCertInstalled([]byte) error { +func isCertInstalled([]byte) bool { return false } diff --git a/internal/certs/installer.go b/internal/certs/installer.go index fd14054f..7d164740 100644 --- a/internal/certs/installer.go +++ b/internal/certs/installer.go @@ -17,6 +17,12 @@ package certs +import "errors" + +var ( + ErrUserCanceledCertificateInstall = errors.New("the user cancelled the authorization dialog") +) + type Installer struct{} func NewInstaller() *Installer { diff --git a/internal/vault/certs.go b/internal/vault/certs.go index ee48e203..1434eef6 100644 --- a/internal/vault/certs.go +++ b/internal/vault/certs.go @@ -66,16 +66,6 @@ func (vault *Vault) SetBridgeTLSCertKey(cert, key []byte) error { }) } -func (vault *Vault) GetCertsInstalled() bool { - return vault.getSafe().Certs.Installed -} - -func (vault *Vault) SetCertsInstalled(installed bool) error { - return vault.modSafe(func(data *Data) { - data.Certs.Installed = installed - }) -} - func readPEMCert(certPEMPath, keyPEMPath string) ([]byte, []byte, error) { certPEM, err := os.ReadFile(filepath.Clean(certPEMPath)) if err != nil { diff --git a/internal/vault/certs_test.go b/internal/vault/certs_test.go index 0a3d7fde..8f5b6187 100644 --- a/internal/vault/certs_test.go +++ b/internal/vault/certs_test.go @@ -31,13 +31,4 @@ func TestVault_TLSCerts(t *testing.T) { cert, key := s.GetBridgeTLSCert() require.NotEmpty(t, cert) require.NotEmpty(t, key) - - // Check the certificates are not installed. - require.False(t, s.GetCertsInstalled()) - - // Install the certificates. - require.NoError(t, s.SetCertsInstalled(true)) - - // Check the certificates are installed. - require.True(t, s.GetCertsInstalled()) } diff --git a/internal/vault/types_certs.go b/internal/vault/types_certs.go index 195a43a1..190cb975 100644 --- a/internal/vault/types_certs.go +++ b/internal/vault/types_certs.go @@ -20,8 +20,7 @@ package vault import "github.com/ProtonMail/proton-bridge/v3/internal/certs" type Certs struct { - Bridge Cert - Installed bool + Bridge Cert // If non-empty, the path to the PEM-encoded certificate file. CustomCertPath string From 43f7a989beac3b7a06fa9769e669672b01098434 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Fri, 18 Aug 2023 09:01:40 +0200 Subject: [PATCH 34/93] feat(GODT-2771): added CLI commands for cert install/uninstall/status check on macOS. --- internal/certs/cert_store_darwin_test.go | 4 +- internal/certs/installer.go | 34 +++++++++++++--- internal/frontend/cli/accounts.go | 12 ++++++ internal/frontend/cli/frontend.go | 52 ++++++++++++++++++------ internal/frontend/cli/system.go | 45 ++++++++++++++++++++ 5 files changed, 128 insertions(+), 19 deletions(-) diff --git a/internal/certs/cert_store_darwin_test.go b/internal/certs/cert_store_darwin_test.go index 2f6fa84f..21cddbac 100644 --- a/internal/certs/cert_store_darwin_test.go +++ b/internal/certs/cert_store_darwin_test.go @@ -40,7 +40,7 @@ func TestCertInKeychain(t *testing.T) { } // This test require human interaction (macOS security prompts), and is disabled by default. -func _TestCertificateTrust(t *testing.T) { +func _TestCertificateTrust(t *testing.T) { //nolint:unused certPEM := generatePEMCertificate(t) require.False(t, isCertTrusted(certPEM)) require.NoError(t, addCertToKeychain(certPEM)) @@ -52,7 +52,7 @@ func _TestCertificateTrust(t *testing.T) { } // This test require human interaction (macOS security prompts), and is disabled by default. -func _TestInstallAndRemove(t *testing.T) { +func _TestInstallAndRemove(t *testing.T) { //nolint:unused certPEM := generatePEMCertificate(t) // fresh install diff --git a/internal/certs/installer.go b/internal/certs/installer.go index 7d164740..39ab0cf5 100644 --- a/internal/certs/installer.go +++ b/internal/certs/installer.go @@ -17,24 +17,48 @@ package certs -import "errors" +import ( + "errors" + + "github.com/sirupsen/logrus" +) var ( ErrUserCanceledCertificateInstall = errors.New("the user cancelled the authorization dialog") ) -type Installer struct{} +type Installer struct { + log *logrus.Entry +} func NewInstaller() *Installer { - return &Installer{} + return &Installer{ + log: logrus.WithField("pkg", "certs"), + } } func (installer *Installer) InstallCert(certPEM []byte) error { - return installCert(certPEM) + installer.log.Info("Installing the Bridge TLS certificate in the OS keychain") + + if err := installCert(certPEM); err != nil { + installer.log.WithError(err).Error("The Bridge TLS certificate could not be installed in the OS keychain") + return err + } + + installer.log.Info("The Bridge TLS certificate was successfully installed in the OS keychain") + return nil } func (installer *Installer) UninstallCert(certPEM []byte) error { - return uninstallCert(certPEM) + installer.log.Info("Uninstalling the Bridge TLS certificate from the OS keychain") + + if err := uninstallCert(certPEM); err != nil { + installer.log.WithError(err).Error("The Bridge TLS certificate could not be uninstalled from the OS keychain") + return err + } + + installer.log.Info("The Bridge TLS certificate was successfully uninstalled from the OS keychain") + return nil } func (installer *Installer) IsCertInstalled(certPEM []byte) bool { diff --git a/internal/frontend/cli/accounts.go b/internal/frontend/cli/accounts.go index cabb7413..bd2a1fa8 100644 --- a/internal/frontend/cli/accounts.go +++ b/internal/frontend/cli/accounts.go @@ -23,6 +23,7 @@ import ( "github.com/ProtonMail/go-proton-api" "github.com/ProtonMail/proton-bridge/v3/internal/bridge" + "github.com/ProtonMail/proton-bridge/v3/internal/certs" "github.com/ProtonMail/proton-bridge/v3/internal/constants" "github.com/ProtonMail/proton-bridge/v3/internal/vault" "github.com/abiosoft/ishell" @@ -297,6 +298,17 @@ func (f *frontendCLI) configureAppleMail(c *ishell.Context) { return } + cert, _ := f.bridge.GetBridgeTLSCert() + installer := certs.NewInstaller() + if !installer.IsCertInstalled(cert) { + f.Println("Apple Mail requires that a TLS certificate for bridge IMAP and SMTP server is installed in your system keychain.") + f.Println("Please provide your credentials in the system popup dialog in order to continue.") + if err := installer.InstallCert(cert); err != nil { + f.printAndLogError(err) + return + } + } + if err := f.bridge.ConfigureAppleMail(context.Background(), user.UserID, user.Addresses[0]); err != nil { f.printAndLogError(err) return diff --git a/internal/frontend/cli/frontend.go b/internal/frontend/cli/frontend.go index 3e84ca68..a3ff74b7 100644 --- a/internal/frontend/cli/frontend.go +++ b/internal/frontend/cli/frontend.go @@ -21,6 +21,7 @@ package cli import ( "errors" "os" + "runtime" "github.com/ProtonMail/gluon/async" "github.com/ProtonMail/proton-bridge/v3/internal/bridge" @@ -145,25 +146,52 @@ func New( }) fe.AddCmd(dohCmd) - // Apple Mail commands. - configureCmd := &ishell.Cmd{ - Name: "configure-apple-mail", - Help: "Configures Apple Mail to use ProtonMail Bridge", - Func: fe.configureAppleMail, + //goland:noinspection GoBoolExpressions + if runtime.GOOS == "darwin" { + // Apple Mail commands. + configureCmd := &ishell.Cmd{ + Name: "configure-apple-mail", + Help: "Configures Apple Mail to use ProtonMail Bridge", + Func: fe.configureAppleMail, + } + fe.AddCmd(configureCmd) } - fe.AddCmd(configureCmd) // TLS commands. - fe.AddCmd(&ishell.Cmd{ - Name: "export-tls-cert", - Help: "Export the TLS certificate used by the Bridge", + certCmd := &ishell.Cmd{ + Name: "cert", + Help: "Manage the TLS certificate used by Bridge", + } + + //goland:noinspection GoBoolExpressions + if runtime.GOOS == "darwin" { + certCmd.AddCmd(&ishell.Cmd{ + Name: "status", + Help: "Check if the TLS certificate used by Bridge is installed in the OS keychain", + Func: fe.tlsCertStatus, + }) + certCmd.AddCmd(&ishell.Cmd{ + Name: "install", + Help: "Install TLS certificate used by Bridge in the OS keychain", + Func: fe.installTLSCert, + }) + certCmd.AddCmd(&ishell.Cmd{ + Name: "uninstall", + Help: "Uninstall the TLS certificate used by Bridge from the OS keychain", + Func: fe.uninstallTLSCert, + }) + } + certCmd.AddCmd(&ishell.Cmd{ + Name: "export", + Help: "Export the TLS certificate used by Bridge", Func: fe.exportTLSCerts, }) - fe.AddCmd(&ishell.Cmd{ - Name: "import-tls-cert", - Help: "Import a TLS certificate to be used by the Bridge", + certCmd.AddCmd(&ishell.Cmd{ + Name: "import", + Help: "Import a TLS certificate to be used by Bridge", Func: fe.importTLSCerts, }) + fe.AddCmd(certCmd) // All mail visibility commands. allMailCmd := &ishell.Cmd{ diff --git a/internal/frontend/cli/system.go b/internal/frontend/cli/system.go index d163db1f..be6d03e4 100644 --- a/internal/frontend/cli/system.go +++ b/internal/frontend/cli/system.go @@ -27,6 +27,7 @@ import ( "strings" "github.com/ProtonMail/proton-bridge/v3/internal/bridge" + "github.com/ProtonMail/proton-bridge/v3/internal/certs" "github.com/ProtonMail/proton-bridge/v3/pkg/ports" "github.com/abiosoft/ishell" ) @@ -240,6 +241,50 @@ func (f *frontendCLI) setGluonLocation(c *ishell.Context) { } } +func (f *frontendCLI) tlsCertStatus(_ *ishell.Context) { + cert, _ := f.bridge.GetBridgeTLSCert() + installer := certs.NewInstaller() + if installer.IsCertInstalled(cert) { + f.Println("The Bridge TLS certificate is already installed in the OS keychain.") + } else { + f.Println("The Bridge TLS certificate is not installed in the OS keychain.") + } +} + +func (f *frontendCLI) installTLSCert(_ *ishell.Context) { + cert, _ := f.bridge.GetBridgeTLSCert() + installer := certs.NewInstaller() + if installer.IsCertInstalled(cert) { + f.printAndLogError(errors.New("the Bridge TLS certificate is already installed in the OS keychain")) + return + } + + f.Println("Please provide your credentials in the system popup dialog in order to continue.") + if err := installer.InstallCert(cert); err != nil { + f.printAndLogError(err) + return + } + + f.Println("The Bridge TLS certificate was successfully installed in the OS keychain.") +} + +func (f *frontendCLI) uninstallTLSCert(_ *ishell.Context) { + cert, _ := f.bridge.GetBridgeTLSCert() + installer := certs.NewInstaller() + if !installer.IsCertInstalled(cert) { + f.printAndLogError(errors.New("the Bridge TLS certificate is not installed in the OS keychain")) + return + } + + f.Println("Please provide your credentials in the system popup dialog in order to continue.") + if err := installer.UninstallCert(cert); err != nil { + f.printAndLogError(err) + return + } + + f.Println("The Bridge TLS certificate was successfully uninstalled from the OS keychain.") +} + func (f *frontendCLI) exportTLSCerts(c *ishell.Context) { if location := f.readStringInAttempts("Enter a path to which to export the TLS certificate used for IMAP and SMTP", c.ReadLine, f.isCacheLocationUsable); location != "" { cert, key := f.bridge.GetBridgeTLSCert() From 65846ff40f6b94a2be568e09d12460976ebed288 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Fri, 18 Aug 2023 13:00:52 +0200 Subject: [PATCH 35/93] feat(GODT-2772): removed warning and outlook selector setup wizard pages. --- .../bridge-gui/bridge-gui/Resources.qrc | 2 - .../qml/SetupWizard/ClientConfigAppleMail.qml | 2 +- .../ClientConfigOutlookSelector.qml | 91 --------------- .../qml/SetupWizard/ClientConfigSelector.qml | 6 +- .../qml/SetupWizard/ClientConfigWarning.qml | 109 ------------------ .../qml/SetupWizard/SetupWizard.qml | 21 ---- 6 files changed, 4 insertions(+), 227 deletions(-) delete mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigOutlookSelector.qml delete mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigWarning.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index 9d2dcaf0..675d2f54 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -109,10 +109,8 @@ qml/SetupWizard/ClientListItem.qml qml/SetupWizard/LeftPane.qml qml/SetupWizard/ClientConfigAppleMail.qml - qml/SetupWizard/ClientConfigOutlookSelector.qml qml/SetupWizard/ClientConfigParameters.qml qml/SetupWizard/ClientConfigSelector.qml - qml/SetupWizard/ClientConfigWarning.qml qml/SetupWizard/SetupWizard.qml qml/SetupWizard/Login.qml qml/SetupWizard/Onboarding.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml index 098bf705..1cd093c5 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml @@ -58,7 +58,7 @@ Item { } function onCertificateInstallSuccess() { certInstallButton.loading = false; - console.error("Certification installed successfully"); + console.error("Certificate installed successfully"); stack.currentIndex = ClientConfigAppleMail.Screen.ProfileInstall; } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigOutlookSelector.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigOutlookSelector.qml deleted file mode 100644 index 180532eb..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigOutlookSelector.qml +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) 2023 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 . -import QtQml -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import QtQuick.Controls.impl -import Proton - -Item { - id: root - - readonly property bool onMacOS: (Backend.goos === "darwin") - readonly property bool onWindows: (Backend.goos === "windows") - property var wizard - - ColumnLayout { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - spacing: 0 - - Label { - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - colorScheme: wizard.colorScheme - text: qsTr("Pick your version of Outlook") - type: Label.LabelType.Heading - } - Item { - Layout.preferredHeight: 72 - } - ClientListItem { - Layout.fillWidth: true - colorScheme: wizard.colorScheme - iconSource: "/qml/icons/ic-microsoft-outlook.svg" - text: "Outlook from Microsoft 365" - - onClicked: { - wizard.clientVersion = "365"; - wizard.showClientWarning(); - } - } - ClientListItem { - Layout.fillWidth: true - colorScheme: wizard.colorScheme - iconSource: "/qml/icons/ic-microsoft-outlook.svg" - text: "Outlook 2019" - - onClicked: { - wizard.clientVersion = "2019"; - wizard.showClientWarning(); - } - } - ClientListItem { - Layout.fillWidth: true - colorScheme: wizard.colorScheme - iconSource: "/qml/icons/ic-microsoft-outlook.svg" - text: "Outlook 2016" - - onClicked: { - wizard.clientVersion = "2016"; - wizard.showClientWarning(); - } - } - Item { - Layout.preferredHeight: 72 - } - Button { - Layout.fillWidth: true - colorScheme: wizard.colorScheme - secondary: true - text: qsTr("Cancel") - - onClicked: { - root.wizard.closeWizard(); - } - } - } -} - diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml index 1d1fd0ef..28db59c9 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml @@ -61,7 +61,7 @@ Item { onClicked: { wizard.client = SetupWizard.Client.MicrosoftOutlook; - wizard.showOutlookSelector(); + wizard.showClientParams(); } } ClientListItem { @@ -72,7 +72,7 @@ Item { onClicked: { wizard.client = SetupWizard.Client.MozillaThunderbird; - wizard.showClientWarning(); + wizard.showClientParams(); } } ClientListItem { @@ -83,7 +83,7 @@ Item { onClicked: { wizard.client = SetupWizard.Client.Generic; - wizard.showClientWarning(); + wizard.showClientParams(); } } Item { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigWarning.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigWarning.qml deleted file mode 100644 index b4db56ba..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigWarning.qml +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) 2023 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 . -import QtQml -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import QtQuick.Controls.impl -import Proton - -// Copyright (c) 2023 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 . -import QtQml -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import QtQuick.Controls.impl -import Proton - -Item { - id: root - - property var wizard - - ColumnLayout { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - spacing: 0 - - Label { - Layout.fillWidth: true - colorScheme: wizard.colorScheme - horizontalAlignment: Text.AlignHCenter - text: qsTr("A word of warning") - type: Label.LabelType.Heading - wrapMode: Text.WordWrap - } - Item { - Layout.preferredHeight: 96 - } - Label { - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - colorScheme: wizard.colorScheme - horizontalAlignment: Text.AlignHCenter - text: qsTr("Do not enter your Proton account password in you email application.") - type: Label.LabelType.Body - wrapMode: Text.WordWrap - } - Item { - Layout.preferredHeight: 96 - } - Label { - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - colorScheme: wizard.colorScheme - horizontalAlignment: Text.AlignHCenter - text: qsTr("We have generated a new password for you. It will work only on this computer, and can safely be entered in your email client.") - type: Label.LabelType.Body - wrapMode: Text.WordWrap - } - Item { - Layout.preferredHeight: 96 - } - Button { - Layout.fillWidth: true - colorScheme: wizard.colorScheme - text: qsTr("I understand") - - onClicked: { - root.wizard.showClientParams(); - } - } - Item { - Layout.preferredHeight: 32 - } - Button { - Layout.fillWidth: true - colorScheme: wizard.colorScheme - secondary: true - text: qsTr("Cancel") - - onClicked: { - root.wizard.closeWizard(); - } - } - } -} - diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 38083975..1c5117a6 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -30,8 +30,6 @@ Item { Onboarding, Login, ClientConfigSelector, - ClientConfigOutlookSelector, - ClientConfigWarning, ClientConfigAppleMail } enum RootStack { @@ -97,11 +95,6 @@ Item { function showClientParams() { rootStackLayout.currentIndex = SetupWizard.RootStack.ClientConfigParameters; } - function showClientWarning() { - rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; - leftContent.showClientConfigWarning(); - rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigWarning; - } function showLogin(username = "") { rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; root.address = ""; @@ -216,20 +209,6 @@ Item { wizard: root } // rightContent stack index 3 - ClientConfigOutlookSelector { - id: clientConfigOutlookSelector - Layout.fillHeight: true - Layout.fillWidth: true - wizard: root - } - // rightContent stack index 4 - ClientConfigWarning { - id: clientConfigWarning - Layout.fillHeight: true - Layout.fillWidth: true - wizard: root - } - // rightContent stack index 5 ClientConfigAppleMail { id: clientConfigAppleMail Layout.fillHeight: true From 6f420f90986c3af3a2fba4ed5d243d03cb2cc37c Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Fri, 18 Aug 2023 17:02:17 +0200 Subject: [PATCH 36/93] feat(GODT-2772): converted setup wizard help link to button with context menu. --- .../bridge-gui/bridge-gui/Resources.qrc | 1 + .../SetupWizard/ClientConfigParameters.qml | 14 ----- .../bridge-gui/qml/SetupWizard/HelpButton.qml | 63 +++++++++++++++++++ .../qml/SetupWizard/SetupWizard.qml | 18 +----- 4 files changed, 67 insertions(+), 29 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index 675d2f54..db266b0d 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -111,6 +111,7 @@ qml/SetupWizard/ClientConfigAppleMail.qml qml/SetupWizard/ClientConfigParameters.qml qml/SetupWizard/ClientConfigSelector.qml + qml/SetupWizard/HelpButton.qml qml/SetupWizard/SetupWizard.qml qml/SetupWizard/Login.qml qml/SetupWizard/Onboarding.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml index 3dbc38d2..039571de 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml @@ -108,19 +108,5 @@ Rectangle { onClicked: wizard.closeWizard() } } - LinkLabel { - id: reportProblemLink - anchors.bottom: parent.bottom - anchors.bottomMargin: 48 - anchors.right: parent.right - colorScheme: wizard.colorScheme - horizontalAlignment: Text.AlignRight - text: link("#", qsTr("Report problem")) - - onLinkActivated: { - wizard.closeWizard(); - wizard.showBugReport(); - } - } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml new file mode 100644 index 00000000..b0c241ea --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml @@ -0,0 +1,63 @@ +// Copyright (c) 2023 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 . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.impl +import Proton + +Button { + id: root + + property var wizard + + anchors.bottom: parent.bottom + anchors.bottomMargin: 24 + anchors.right: parent.right + anchors.rightMargin: 24 + colorScheme: wizard.colorScheme + height: 36 + horizontalPadding: 0 + icon.source: "/qml/icons/ic-question-circle.svg" + width: 36 + + onClicked: { + menu.popup(-menu.width + root.width, -menu.height); + } + + Menu { + id: menu + colorScheme: root.colorScheme + modal: true + + MenuItem { + id: getHelpItem + colorScheme: root.colorScheme + text: qsTr("Get help") + + onClicked: { + console.error("Get help"); + } + } + MenuItem { + id: reportAProblemItem + colorScheme: root.colorScheme + text: qsTr("Report a problem") + + onClicked: { + console.error("Report a problem"); + } + } + } +} \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 1c5117a6..b418aa7a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -216,21 +216,6 @@ Item { wizard: root } } - LinkLabel { - id: reportProblemLink - anchors.bottom: parent.bottom - anchors.bottomMargin: 48 - anchors.horizontalCenter: parent.horizontalCenter - colorScheme: root.colorScheme - horizontalAlignment: Text.AlignRight - text: link("#", qsTr("Report problem")) - width: 444 - - onLinkActivated: { - closeWizard(); - showBugReport(); - } - } } } @@ -242,5 +227,8 @@ Item { wizard: root } } + HelpButton { + wizard: root + } } From 53ea5e9adc12be90252bedc76b7ec308bbc50606 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Mon, 21 Aug 2023 07:23:41 +0200 Subject: [PATCH 37/93] feat(GODT-2772): fix aliasing in protonmail wordmark on Windows. --- .../bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index b418aa7a..0b0718d6 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -159,9 +159,10 @@ Item { anchors.bottom: parent.bottom anchors.bottomMargin: 48 anchors.horizontalCenter: parent.horizontalCenter - fillMode: Image.PreserveAspectFit height: 24 - mipmap: true + width: 136 + sourceSize.height: 24 + sourceSize.width: 136 source: root.colorScheme.mail_logo_with_wordmark } } From 81afc5fb1fcf99599f685288d224e4b6ee403f3c Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Mon, 21 Aug 2023 09:07:48 +0200 Subject: [PATCH 38/93] feat(GODT-2772): new onboarding layout. --- .../bridge-gui/bridge-gui/qml/MainWindow.qml | 6 +-- .../bridge-gui/qml/SetupWizard/HelpButton.qml | 9 ++-- .../bridge-gui/qml/SetupWizard/LeftPane.qml | 8 +-- .../bridge-gui/qml/SetupWizard/Onboarding.qml | 17 ++----- .../qml/SetupWizard/SetupWizard.qml | 24 ++++----- .../qml/SetupWizard/StepDescriptionBox.qml | 1 + .../qml/icons/img-mail-logo-wordmark-dark.svg | 49 +++++++++++-------- .../qml/icons/img-mail-logo-wordmark.svg | 49 +++++++++++-------- 8 files changed, 86 insertions(+), 77 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index 93f3555a..38b739b4 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -28,17 +28,17 @@ ApplicationWindow { function layoutForUserCount(userCount) { if (userCount === 0) { - showLogin(); + contentLayout.currentIndex = 1; + setupWizard.showOnboarding(); return; } const u = Backend.users.get(0); if (!u) { console.trace(); - console.log("empty user"); - setupWizard.showOnboarding(); return; } if ((userCount === 1) && (u.state === EUserState.SignedOut)) { + contentLayout.currentIndex = 1; setupWizard.showLogin(u.primaryEmailOrUsername()); } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml index b0c241ea..1d9ac95c 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml @@ -23,14 +23,15 @@ Button { property var wizard anchors.bottom: parent.bottom - anchors.bottomMargin: 24 + anchors.bottomMargin: 32 anchors.right: parent.right - anchors.rightMargin: 24 + anchors.rightMargin: 32 colorScheme: wizard.colorScheme - height: 36 horizontalPadding: 0 icon.source: "/qml/icons/ic-question-circle.svg" - width: 36 + icon.height: 24 + icon.width: 24 + verticalPadding: 0 onClicked: { menu.popup(-menu.width + root.width, -menu.height); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 8edd5842..011078d2 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -100,8 +100,8 @@ Item { ColumnLayout { anchors.left: parent.left anchors.right: parent.right - anchors.top: parent.top - spacing: 0 + anchors.verticalCenter: parent.verticalCenter + spacing: 16 Image { id: icon @@ -117,7 +117,6 @@ Item { id: titleLabel Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - Layout.topMargin: 16 colorScheme: wizard.colorScheme horizontalAlignment: Text.AlignHCenter text: "" @@ -128,7 +127,6 @@ Item { id: descriptionLabel Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - Layout.topMargin: 96 colorScheme: wizard.colorScheme horizontalAlignment: Text.AlignHCenter text: "" @@ -138,14 +136,12 @@ Item { LinkLabel { id: linkLabel1 Layout.alignment: Qt.AlignHCenter - Layout.topMargin: 96 colorScheme: wizard.colorScheme visible: (text !== "") } LinkLabel { id: linkLabel2 Layout.alignment: Qt.AlignHCenter - Layout.topMargin: 16 colorScheme: wizard.colorScheme visible: (text !== "") } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml index 5338cceb..bebe474c 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml @@ -19,23 +19,14 @@ import "." as Proton Item { id: root - property var wizard ColumnLayout { anchors.left: parent.left anchors.right: parent.right - anchors.top: parent.top - spacing: 96 + anchors.verticalCenter: parent.verticalCenter + spacing: 24 - Label { - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - colorScheme: wizard.colorScheme - horizontalAlignment: Text.AlignHCenter - text: qsTr("Two-step process") - type: Label.LabelType.Heading - } StepDescriptionBox { colorScheme: wizard.colorScheme description: qsTr("Connect Bridge to your Proton account") @@ -47,14 +38,14 @@ Item { colorScheme: wizard.colorScheme description: qsTr("Connect your email client to Bridge") icon: "/qml/icons/img-mail-clients.svg" - iconSize: 64 + iconSize: 48 title: qsTr("Step 2") } Button { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true colorScheme: wizard.colorScheme - text: qsTr("Let's start") + text: qsTr("Start setup") onClicked: wizard.showLogin() } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 0b0718d6..37658dbf 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -105,6 +105,8 @@ Item { } function showOnboarding() { rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; + root.address = "" + root.user = null leftContent.showOnboarding(); rightContent.currentIndex = SetupWizard.ContentStack.Onboarding; } @@ -146,23 +148,23 @@ Item { LeftPane { id: leftContent anchors.bottom: parent.bottom - anchors.bottomMargin: 96 + anchors.bottomMargin: 92 anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top - anchors.topMargin: 96 + anchors.topMargin: 40 clip: true - width: 444 + width: 364 wizard: root } Image { id: mailLogoWithWordmark anchors.bottom: parent.bottom - anchors.bottomMargin: 48 + anchors.bottomMargin: 40 anchors.horizontalCenter: parent.horizontalCenter - height: 24 - width: 136 - sourceSize.height: 24 - sourceSize.width: 136 + height: 36 + width: 134 + sourceSize.height: 36 + sourceSize.width: 134 source: root.colorScheme.mail_logo_with_wordmark } } @@ -175,13 +177,13 @@ Item { StackLayout { id: rightContent anchors.bottom: parent.bottom - anchors.bottomMargin: 96 + anchors.bottomMargin: 92 anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top - anchors.topMargin: 96 + anchors.topMargin: 40 clip: true currentIndex: 0 - width: 444 + width: 364 // rightContent stack index 0 Onboarding { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml index f76962b7..ff4bf37f 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml @@ -59,6 +59,7 @@ Item { Layout.fillWidth: true colorScheme: root.colorScheme text: root.description + color: root.colorScheme.text_weak type: Label.LabelType.Body verticalAlignment: Text.AlignTop } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark-dark.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark-dark.svg index b1d4e727..8e9e0d41 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark-dark.svg +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark-dark.svg @@ -1,25 +1,34 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - + + + - - - + + + + + + + + + + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark.svg index 76b55db8..30212b3c 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark.svg +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark.svg @@ -1,25 +1,34 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - + + + - - - + + + + + + + + + + From 6e86c95640199aafd1875ae5578ab00edabe0401 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Mon, 21 Aug 2023 13:34:16 +0200 Subject: [PATCH 39/93] feat(GODT-2772): new login layout. --- .../bridge-gui-tester/GRPCService.cpp | 4 +- .../bridge-gui/bridge-gui/QMLBackend.h | 2 +- .../bridge-gui/qml/SetupWizard/LeftPane.qml | 32 +- .../bridge-gui/qml/SetupWizard/Login.qml | 592 +++++---- .../qml/SetupWizard/SetupWizard.qml | 5 - .../bridgepp/bridgepp/GRPC/EventFactory.cpp | 3 +- .../bridgepp/bridgepp/GRPC/EventFactory.h | 2 +- .../bridgepp/bridgepp/GRPC/GRPCClient.cpp | 2 +- .../bridgepp/bridgepp/GRPC/GRPCClient.h | 4 +- internal/frontend/grpc/bridge.pb.go | 1142 +++++++++-------- internal/frontend/grpc/bridge.proto | 4 +- internal/frontend/grpc/event_factory.go | 4 +- internal/frontend/grpc/service_methods.go | 4 +- internal/frontend/grpc/service_stream.go | 2 +- 14 files changed, 920 insertions(+), 882 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp index 0b75e2b8..4e8fb8f0 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp @@ -400,7 +400,7 @@ Status GRPCService::Login(ServerContext *, LoginRequest const *request, Empty *) return Status::OK; } if (usersTab.nextUserTwoPasswordsRequired()) { - qtProxy_.sendDelayedEvent(newLoginTwoPasswordsRequestedEvent()); + qtProxy_.sendDelayedEvent(newLoginTwoPasswordsRequestedEvent(loginUsername_)); return Status::OK; } @@ -425,7 +425,7 @@ Status GRPCService::Login2FA(ServerContext *, LoginRequest const *request, Empty return Status::OK; } if (usersTab.nextUserTwoPasswordsRequired()) { - qtProxy_.sendDelayedEvent(newLoginTwoPasswordsRequestedEvent()); + qtProxy_.sendDelayedEvent(newLoginTwoPasswordsRequestedEvent(loginUsername_)); return Status::OK; } diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h index 159e5388..83ae80b8 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h @@ -233,7 +233,7 @@ signals: // Signals received from the Go backend, to be forwarded to QML void login2FARequested(QString const &username); ///< Signal for the 'login2FARequested' gRPC stream event. void login2FAError(QString const &errorMsg); ///< Signal for the 'login2FAError' gRPC stream event. void login2FAErrorAbort(QString const &errorMsg); ///< Signal for the 'login2FAErrorAbort' gRPC stream event. - void login2PasswordRequested(); ///< Signal for the 'login2PasswordRequested' gRPC stream event. + void login2PasswordRequested(QString const &username); ///< Signal for the 'login2PasswordRequested' gRPC stream event. void login2PasswordError(QString const &errorMsg); ///< Signal for the 'login2PasswordError' gRPC stream event. void login2PasswordErrorAbort(QString const &errorMsg); ///< Signal for the 'login2PasswordErrorAbort' gRPC stream event. void loginFinished(int index, bool wasSignedOut); ///< Signal for the 'loginFinished' gRPC stream event. diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 011078d2..05e0ece9 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -45,33 +45,16 @@ Item { icon.source = "/qml/icons/img-mail-clients.svg"; } function showLogin() { - descriptionLabel.text = qsTr("Let's start by signing in to your Proton account."); - linkLabel1.setLink("https://proton.me/mail/pricing", qsTr("Create or upgrade your account")); - linkLabel2.clear(); - showLoginCommon(); - } + showOnboarding() + } function showLogin2FA() { - descriptionLabel.text = qsTr("You have enabled two-factor authentication. Please enter the 6-digit code provided by your authenticator application."); - linkLabel1.clear(); - linkLabel2.clear(); - showLoginCommon(); - } - function showLoginCommon() { - titleLabel.text = qsTr("Sign in to your Proton Account"); - icon.Layout.preferredHeight = 72; - icon.Layout.preferredWidth = 72; - icon.source = "/qml/icons/ic-bridge.svg"; - icon.sourceSize.height = 128; - icon.sourceSize.width = 128; + showOnboarding() } function showLoginMailboxPassword() { - descriptionLabel.text = qsTr("You have secured your account with a separate mailbox password."); - linkLabel1.clear(); - linkLabel2.clear(); - showLoginCommon(); + showOnboarding() } function showOnboarding() { - titleLabel.text = qsTr("Welcome to\nProton Mail Bridge"); + titleLabel.text = (Backend.users.count === 0) ? qsTr("Welcome to\nProton Mail Bridge") : qsTr("Add a Proton Mail account"); descriptionLabel.text = qsTr("Bridge is the gateway between your Proton account and your email client. It runs in the background and encrypts and decrypts your messages seamlessly. "); linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why do I need Bridge?")); linkLabel2.clear(); @@ -81,11 +64,6 @@ Item { icon.sourceSize.height = 148; icon.sourceSize.width = 265; } - function showOutlookSelector() { - showClientConfigCommon(); - linkLabel1.setLink("https://proton.me/support/bridge", qsTr("My version of Outlook is not listed")); - linkLabel2.clear(); - } Connections { function onLogin2FARequested() { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml index 4f557b62..5a10398d 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml @@ -43,9 +43,6 @@ FocusScope { mailboxPasswordLayout.reset(); } - implicitHeight: children[0].implicitHeight - implicitWidth: children[0].implicitWidth - StackLayout { id: stackLayout function loginFailed() { @@ -91,9 +88,10 @@ FocusScope { root.reset(); errorLabel.text = qsTr("Incorrect login credentials. Please try again."); } - function onLogin2PasswordRequested() { + function onLogin2PasswordRequested(username) { console.assert(stackLayout.currentIndex === Login.RootStack.Login || stackLayout.currentIndex === Login.RootStack.TOTP, "Unexpected login2PasswordRequested"); stackLayout.currentIndex = Login.RootStack.MailboxPassword; + mailboxPasswordUsernameLabel.text = username; secondPasswordTextField.focus = true; } function onLoginAlreadyLoggedIn(_) { @@ -124,299 +122,353 @@ FocusScope { target: Backend } - ColumnLayout { - id: loginLayout - function reset(clearUsername = false) { - signInButton.loading = false; - errorLabel.text = ""; - usernameTextField.enabled = true; - usernameTextField.error = false; - usernameTextField.errorString = ""; - usernameTextField.focus = true; - if (clearUsername) { - usernameTextField.text = ""; + Item { + ColumnLayout { + id: loginLayout + function reset(clearUsername = false) { + signInButton.loading = false; + errorLabel.text = ""; + usernameTextField.enabled = true; + usernameTextField.error = false; + usernameTextField.errorString = ""; + usernameTextField.focus = true; + if (clearUsername) { + usernameTextField.text = ""; + } + passwordTextField.enabled = true; + passwordTextField.error = false; + passwordTextField.errorString = ""; + passwordTextField.text = ""; } - passwordTextField.enabled = true; - passwordTextField.error = false; - passwordTextField.errorString = ""; - passwordTextField.text = ""; - } - spacing: 0 + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: 16 - Label { - Layout.alignment: Qt.AlignHCenter - colorScheme: wizard.colorScheme - text: qsTr("Sign in") - type: Label.LabelType.Heading - } - Label { - id: subTitle - Layout.alignment: Qt.AlignHCenter - Layout.topMargin: 8 - color: wizard.colorScheme.text_weak - colorScheme: wizard.colorScheme - text: qsTr("Enter your Proton Account details.") - type: Label.LabelType.Lead - } - RowLayout { - Layout.fillWidth: true - Layout.topMargin: 48 - spacing: 0 - visible: errorLabel.text.length > 0 + ColumnLayout { + Layout.fillWidth: true + spacing: 8 - ColorImage { - color: wizard.colorScheme.signal_danger - height: errorLabel.lineHeight - source: "/qml/icons/ic-exclamation-circle-filled.svg" - sourceSize.height: errorLabel.lineHeight + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Sign in") + type: Label.LabelType.Title + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + color: wizard.colorScheme.text_weak + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Enter your Proton Account details.") + type: Label.LabelType.Body + } + } + RowLayout { + Layout.fillWidth: true + spacing: 0 + visible: errorLabel.text.length > 0 + + ColorImage { + color: wizard.colorScheme.signal_danger + height: errorLabel.lineHeight + source: "/qml/icons/ic-exclamation-circle-filled.svg" + sourceSize.height: errorLabel.lineHeight + } + Label { + id: errorLabel + Layout.fillWidth: true + Layout.leftMargin: 4 + color: wizard.colorScheme.signal_danger + colorScheme: wizard.colorScheme + type: root.error ? Label.LabelType.Caption_semibold : Label.LabelType.Caption + wrapMode: Text.WordWrap + } + } + TextField { + id: usernameTextField + Layout.fillWidth: true + colorScheme: wizard.colorScheme + focus: true + label: qsTr("Email or username") + validateOnEditingFinished: false + validator: function (str) { + if (str.length === 0) { + return qsTr("Enter email or username"); + } + } + + onAccepted: passwordTextField.forceActiveFocus() + onTextChanged: { + // remove "invalid username / password error" + if (error || errorLabel.text.length > 0) { + errorLabel.text = ""; + usernameTextField.error = false; + passwordTextField.error = false; + } + } + } + TextField { + id: passwordTextField + Layout.fillWidth: true + colorScheme: wizard.colorScheme + echoMode: TextInput.Password + label: qsTr("Password") + validateOnEditingFinished: false + validator: function (str) { + if (str.length === 0) { + return qsTr("Enter password"); + } + } + + onAccepted: signInButton.checkAndSignIn() + onTextChanged: { + // remove "invalid username / password error" + if (error || errorLabel.text.length > 0) { + errorLabel.text = ""; + usernameTextField.error = false; + passwordTextField.error = false; + } + } + } + Button { + id: signInButton + function checkAndSignIn() { + usernameTextField.validate(); + passwordTextField.validate(); + if (usernameTextField.error || passwordTextField.error) { + return; + } + usernameTextField.enabled = false; + passwordTextField.enabled = false; + loading = true; + Backend.login(usernameTextField.text, Qt.btoa(passwordTextField.text)); + } + + Layout.fillWidth: true + colorScheme: wizard.colorScheme + enabled: !loading + text: loading ? qsTr("Signing in") : qsTr("Sign in") + + onClicked: { + checkAndSignIn(); + } + } + Button { + Layout.fillWidth: true + colorScheme: wizard.colorScheme + enabled: !signInButton.loading + secondary: true + text: qsTr("Cancel") + + onClicked: { + root.abort(); + } + } + LinkLabel { + id: linkLabel + Layout.alignment: Qt.AlignHCenter + colorScheme: wizard.colorScheme + text: linkLabel.link("https://proton.me/mail/pricing", qsTr("Create or upgrade your account")) + } + } + } + Item { + ColumnLayout { + id: totpLayout + function reset() { + twoFAButton.loading = false; + twoFactorPasswordTextField.enabled = true; + twoFactorPasswordTextField.error = false; + twoFactorPasswordTextField.errorString = ""; + twoFactorPasswordTextField.text = ""; + } + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: 16 + + ColumnLayout { + Layout.fillWidth: true + spacing: 8 + + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Two-factor authentication") + type: Label.LabelType.Title + } + Label { + id: twoFactorUsernameLabel + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + color: wizard.colorScheme.text_weak + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: "" + type: Label.LabelType.Body + } } Label { - id: errorLabel + id: descriptionLabel + Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - Layout.leftMargin: 4 - color: wizard.colorScheme.signal_danger colorScheme: wizard.colorScheme - type: root.error ? Label.LabelType.Caption_semibold : Label.LabelType.Caption + horizontalAlignment: Text.AlignHCenter + text: qsTr("You have enabled two-factor authentication. Please enter the 6-digit code provided by your authenticator application.") + type: Label.LabelType.Body wrapMode: Text.WordWrap } - } - TextField { - id: usernameTextField - Layout.fillWidth: true - Layout.topMargin: 48 - colorScheme: wizard.colorScheme - focus: true - label: qsTr("Email or username") - validateOnEditingFinished: false - validator: function (str) { - if (str.length === 0) { - return qsTr("Enter email or username"); + TextField { + id: twoFactorPasswordTextField + Layout.fillWidth: true + colorScheme: wizard.colorScheme + label: qsTr("Two-factor code") + validateOnEditingFinished: false + validator: function (str) { + if (str.length === 0) { + return qsTr("Enter the 6-digit code"); + } } - } - onAccepted: passwordTextField.forceActiveFocus() - onTextChanged: { - // remove "invalid username / password error" - if (error || errorLabel.text.length > 0) { - errorLabel.text = ""; - usernameTextField.error = false; - passwordTextField.error = false; - } - } - } - TextField { - id: passwordTextField - Layout.fillWidth: true - Layout.topMargin: 48 - colorScheme: wizard.colorScheme - echoMode: TextInput.Password - label: qsTr("Password") - validateOnEditingFinished: false - validator: function (str) { - if (str.length === 0) { - return qsTr("Enter password"); - } - } - - onAccepted: signInButton.checkAndSignIn() - onTextChanged: { - // remove "invalid username / password error" - if (error || errorLabel.text.length > 0) { - errorLabel.text = ""; - usernameTextField.error = false; - passwordTextField.error = false; - } - } - } - Button { - id: signInButton - function checkAndSignIn() { - usernameTextField.validate(); - passwordTextField.validate(); - if (usernameTextField.error || passwordTextField.error) { - return; - } - usernameTextField.enabled = false; - passwordTextField.enabled = false; - loading = true; - Backend.login(usernameTextField.text, Qt.btoa(passwordTextField.text)); - } - - Layout.fillWidth: true - Layout.topMargin: 48 - colorScheme: wizard.colorScheme - enabled: !loading - text: loading ? qsTr("Signing in") : qsTr("Sign in") - - onClicked: { - checkAndSignIn(); - } - } - Button { - Layout.fillWidth: true - Layout.topMargin: 32 - colorScheme: wizard.colorScheme - enabled: !signInButton.loading - secondary: true - text: qsTr("Cancel") - - onClicked: { - root.abort(); - } - } - } - ColumnLayout { - id: totpLayout - function reset() { - twoFAButton.loading = false; - twoFactorPasswordTextField.enabled = true; - twoFactorPasswordTextField.error = false; - twoFactorPasswordTextField.errorString = ""; - twoFactorPasswordTextField.text = ""; - } - - spacing: 0 - - Label { - Layout.alignment: Qt.AlignCenter - colorScheme: wizard.colorScheme - text: qsTr("Two-factor authentication") - type: Label.LabelType.Heading - } - Label { - id: twoFactorUsernameLabel - Layout.alignment: Qt.AlignCenter - Layout.topMargin: 8 - color: wizard.colorScheme.text_weak - colorScheme: wizard.colorScheme - type: Label.LabelType.Lead - } - TextField { - id: twoFactorPasswordTextField - Layout.fillWidth: true - Layout.topMargin: 32 - assistiveText: qsTr("Enter the 6-digit code") - colorScheme: wizard.colorScheme - label: qsTr("Two-factor code") - validateOnEditingFinished: false - validator: function (str) { - if (str.length === 0) { - return qsTr("Enter the 6-digit code"); - } - } - - onAccepted: { - twoFAButton.onClicked(); - } - onTextChanged: { - if (text.length >= 6) { + onAccepted: { twoFAButton.onClicked(); } - } - } - Button { - id: twoFAButton - Layout.fillWidth: true - Layout.topMargin: 48 - colorScheme: wizard.colorScheme - enabled: !loading - text: loading ? qsTr("Authenticating") : qsTr("Authenticate") - - onClicked: { - twoFactorPasswordTextField.validate(); - if (twoFactorPasswordTextField.error) { - return; + onTextChanged: { + if (text.length >= 6) { + twoFAButton.onClicked(); + } } - twoFactorPasswordTextField.enabled = false; - loading = true; - Backend.login2FA(usernameTextField.text, Qt.btoa(twoFactorPasswordTextField.text)); } - } - Button { - Layout.fillWidth: true - Layout.topMargin: 32 - colorScheme: wizard.colorScheme - enabled: !twoFAButton.loading - secondary: true - text: qsTr("Cancel") + Button { + id: twoFAButton + Layout.fillWidth: true + colorScheme: wizard.colorScheme + enabled: !loading + text: loading ? qsTr("Authenticating") : qsTr("Authenticate") - onClicked: { - root.abort(); + onClicked: { + twoFactorPasswordTextField.validate(); + if (twoFactorPasswordTextField.error) { + return; + } + twoFactorPasswordTextField.enabled = false; + loading = true; + Backend.login2FA(usernameTextField.text, Qt.btoa(twoFactorPasswordTextField.text)); + } + } + Button { + Layout.fillWidth: true + colorScheme: wizard.colorScheme + enabled: !twoFAButton.loading + secondary: true + text: qsTr("Cancel") + + onClicked: { + root.abort(); + } } } } - ColumnLayout { - id: mailboxPasswordLayout - function reset() { - secondPasswordButton.loading = false; - secondPasswordTextField.enabled = true; - secondPasswordTextField.error = false; - secondPasswordTextField.errorString = ""; - secondPasswordTextField.text = ""; - } + Item { + ColumnLayout { + id: mailboxPasswordLayout + function reset() { + secondPasswordButton.loading = false; + secondPasswordTextField.enabled = true; + secondPasswordTextField.error = false; + secondPasswordTextField.errorString = ""; + secondPasswordTextField.text = ""; + } - spacing: 0 + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: 16 - Label { - Layout.alignment: Qt.AlignCenter - colorScheme: wizard.colorScheme - text: qsTr("Unlock your mailbox") - type: Label.LabelType.Heading - } - Label { - Layout.alignment: Qt.AlignCenter - Layout.topMargin: 8 - color: wizard.colorScheme.text_weak - colorScheme: wizard.colorScheme - type: Label.LabelType.Lead - } - TextField { - id: secondPasswordTextField - Layout.fillWidth: true - Layout.topMargin: 48 - colorScheme: wizard.colorScheme - echoMode: TextInput.Password - label: qsTr("Mailbox password") - validateOnEditingFinished: false - validator: function (str) { - if (str.length === 0) { - return qsTr("Enter password"); + ColumnLayout { + Layout.fillWidth: true + spacing: 8 + + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Unlock your mailbox") + type: Label.LabelType.Title + } + Label { + id: mailboxPasswordUsernameLabel + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + color: wizard.colorScheme.text_weak + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: "" + type: Label.LabelType.Body } } - - onAccepted: { - secondPasswordButton.onClicked(); + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("You have secured your account with a separate mailbox password.") + type: Label.LabelType.Body + wrapMode: Text.WordWrap } - } - Button { - id: secondPasswordButton - Layout.fillWidth: true - Layout.topMargin: 48 - colorScheme: wizard.colorScheme - enabled: !loading - text: loading ? qsTr("Unlocking") : qsTr("Unlock") - - onClicked: { - secondPasswordTextField.validate(); - if (secondPasswordTextField.error) { - return; + TextField { + id: secondPasswordTextField + Layout.fillWidth: true + colorScheme: wizard.colorScheme + echoMode: TextInput.Password + label: qsTr("Mailbox password") + validateOnEditingFinished: false + validator: function (str) { + if (str.length === 0) { + return qsTr("Enter password"); + } } - secondPasswordTextField.enabled = false; - loading = true; - Backend.login2Password(usernameTextField.text, Qt.btoa(secondPasswordTextField.text)); - } - } - Button { - Layout.fillWidth: true - Layout.topMargin: 32 - colorScheme: wizard.colorScheme - enabled: !secondPasswordButton.loading - secondary: true - text: qsTr("Cancel") - onClicked: { - root.abort(); + onAccepted: { + secondPasswordButton.onClicked(); + } + } + Button { + id: secondPasswordButton + Layout.fillWidth: true + colorScheme: wizard.colorScheme + enabled: !loading + text: loading ? qsTr("Unlocking") : qsTr("Unlock") + + onClicked: { + secondPasswordTextField.validate(); + if (secondPasswordTextField.error) { + return; + } + secondPasswordTextField.enabled = false; + loading = true; + Backend.login2Password(usernameTextField.text, Qt.btoa(secondPasswordTextField.text)); + } + } + Button { + Layout.fillWidth: true + colorScheme: wizard.colorScheme + enabled: !secondPasswordButton.loading + secondary: true + text: qsTr("Cancel") + + onClicked: { + root.abort(); + } } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 37658dbf..5a6a1046 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -110,11 +110,6 @@ Item { leftContent.showOnboarding(); rightContent.currentIndex = SetupWizard.ContentStack.Onboarding; } - function showOutlookSelector() { - rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; - leftContent.showOutlookSelector(); - rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigOutlookSelector; - } Connections { function onLoginFinished(userIndex, wasSignedOut) { diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.cpp index 6780d171..dd6f270a 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.cpp @@ -278,8 +278,9 @@ SPStreamEvent newLoginTfaRequestedEvent(QString const &username) { /// \param[in] username The username. /// \return The event. //**************************************************************************************************************************************************** -SPStreamEvent newLoginTwoPasswordsRequestedEvent() { +SPStreamEvent newLoginTwoPasswordsRequestedEvent(QString const &username) { auto event = new ::grpc::LoginTwoPasswordsRequestedEvent; + event->set_username(username.toStdString()); auto loginEvent = new grpc::LoginEvent; loginEvent->set_allocated_twopasswordrequested(event); return wrapLoginEvent(loginEvent); diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.h index 1c2d31b9..32f57c7f 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.h @@ -42,7 +42,7 @@ SPStreamEvent newShowMainWindowEvent(); ///< Create a new ShowMainWindowEvent ev // Login events SPStreamEvent newLoginError(grpc::LoginErrorType error, QString const &message); ///< Create a new LoginError event. SPStreamEvent newLoginTfaRequestedEvent(QString const &username); ///< Create a new LoginTfaRequestedEvent event. -SPStreamEvent newLoginTwoPasswordsRequestedEvent(); ///< Create a new LoginTwoPasswordsRequestedEvent event. +SPStreamEvent newLoginTwoPasswordsRequestedEvent(QString const &username); ///< Create a new LoginTwoPasswordsRequestedEvent event. SPStreamEvent newLoginFinishedEvent(QString const &userID, bool wasSignedOut); ///< Create a new LoginFinishedEvent event. SPStreamEvent newLoginAlreadyLoggedInEvent(QString const &userID); ///< Create a new LoginAlreadyLoggedInEvent event. diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp index 82cc382a..3c66c5aa 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp @@ -1212,7 +1212,7 @@ void GRPCClient::processLoginEvent(LoginEvent const &event) { break; case LoginEvent::kTwoPasswordRequested: this->logTrace("Login event received: TwoPasswordRequested."); - emit login2PasswordRequested(); + emit login2PasswordRequested(QString::fromStdString(event.twopasswordrequested().username())); break; case LoginEvent::kFinished: { this->logTrace("Login event received: Finished."); diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h index 1dbf2624..f77acf84 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h @@ -146,10 +146,10 @@ signals: void loginUsernamePasswordError(QString const &errMsg); void loginFreeUserError(); void loginConnectionError(QString const &errMsg); - void login2FARequested(QString const &userName); + void login2FARequested(QString const &username); void login2FAError(QString const &errMsg); void login2FAErrorAbort(QString const &errMsg); - void login2PasswordRequested(); + void login2PasswordRequested(QString const &username); void login2PasswordError(QString const &errMsg); void login2PasswordErrorAbort(QString const &errMsg); void loginFinished(QString const &userID, bool wasSignedOut); diff --git a/internal/frontend/grpc/bridge.pb.go b/internal/frontend/grpc/bridge.pb.go index d43c4f64..c3cdf92b 100644 --- a/internal/frontend/grpc/bridge.pb.go +++ b/internal/frontend/grpc/bridge.pb.go @@ -2277,6 +2277,8 @@ type LoginTwoPasswordsRequestedEvent struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + + Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` } func (x *LoginTwoPasswordsRequestedEvent) Reset() { @@ -2311,6 +2313,13 @@ func (*LoginTwoPasswordsRequestedEvent) Descriptor() ([]byte, []int) { return file_bridge_proto_rawDescGZIP(), []int{29} } +func (x *LoginTwoPasswordsRequestedEvent) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + type LoginFinishedEvent struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -4743,593 +4752,594 @@ var file_bridge_proto_rawDesc = []byte{ 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x66, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, - 0x6d, 0x65, 0x22, 0x21, 0x0a, 0x1f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x77, 0x6f, 0x50, 0x61, + 0x6d, 0x65, 0x22, 0x3d, 0x0a, 0x1f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x77, 0x6f, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x50, 0x0a, 0x12, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x69, - 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, - 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, - 0x72, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x77, 0x61, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, - 0x4f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x77, 0x61, 0x73, 0x53, 0x69, - 0x67, 0x6e, 0x65, 0x64, 0x4f, 0x75, 0x74, 0x22, 0xb9, 0x04, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, - 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x40, 0x0a, 0x0b, 0x6d, 0x61, 0x6e, 0x75, 0x61, - 0x6c, 0x52, 0x65, 0x61, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, - 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, - 0x52, 0x65, 0x61, 0x64, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x6d, 0x61, - 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x61, 0x64, 0x79, 0x12, 0x58, 0x0a, 0x13, 0x6d, 0x61, 0x6e, - 0x75, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, - 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x13, - 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, - 0x64, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x46, 0x6f, 0x72, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x66, 0x6f, - 0x72, 0x63, 0x65, 0x12, 0x53, 0x0a, 0x13, 0x73, 0x69, 0x6c, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, - 0x6c, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, - 0x64, 0x48, 0x00, 0x52, 0x13, 0x73, 0x69, 0x6c, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x12, 0x47, 0x0a, 0x0f, 0x69, 0x73, 0x4c, 0x61, - 0x74, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, - 0x73, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x00, - 0x52, 0x0f, 0x69, 0x73, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x41, 0x0a, 0x0d, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, - 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, - 0x68, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0d, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, - 0x73, 0x68, 0x65, 0x64, 0x12, 0x44, 0x0a, 0x0e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, - 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, - 0x65, 0x6e, 0x74, 0x22, 0x3d, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x22, 0x32, 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x75, - 0x61, 0x6c, 0x52, 0x65, 0x61, 0x64, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x20, 0x0a, 0x1e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, - 0x64, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x2c, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x1b, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, - 0x64, 0x65, 0x64, 0x22, 0x17, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x73, 0x4c, - 0x61, 0x74, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x15, 0x0a, 0x13, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, - 0x68, 0x65, 0x64, 0x22, 0x16, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x22, 0xeb, 0x01, 0x0a, 0x0e, - 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x31, - 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, - 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x12, 0x43, 0x0a, 0x0b, 0x70, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, - 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x70, 0x61, 0x74, 0x68, 0x43, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x58, 0x0a, 0x12, 0x70, 0x61, 0x74, 0x68, 0x43, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, - 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6e, - 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x70, 0x61, - 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, - 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x43, 0x0a, 0x13, 0x44, 0x69, 0x73, - 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x12, 0x2c, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2f, - 0x0a, 0x19, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x43, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, - 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, - 0x22, 0x0a, 0x20, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, - 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x22, 0xbf, 0x02, 0x0a, 0x17, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, - 0x3a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x64, 0x0a, 0x19, 0x6d, - 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, - 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x19, 0x6d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x64, 0x12, 0x79, 0x0a, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x46, 0x69, 0x6e, - 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x67, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x46, 0x69, 0x6e, 0x69, 0x73, - 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x20, 0x63, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, - 0x69, 0x6e, 0x67, 0x73, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, - 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x55, 0x0a, 0x1c, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x35, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x69, 0x6c, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x54, 0x0a, 0x1e, - 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x32, - 0x0a, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, - 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x22, 0x27, 0x0a, 0x25, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x46, 0x69, - 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xff, 0x01, 0x0a, 0x0d, - 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x5b, 0x0a, - 0x16, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x46, - 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, - 0x67, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, - 0x61, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x48, 0x00, 0x52, 0x16, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, - 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x40, 0x0a, 0x0d, 0x68, 0x61, - 0x73, 0x4e, 0x6f, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x61, 0x73, 0x4e, 0x6f, 0x4b, 0x65, - 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x68, - 0x61, 0x73, 0x4e, 0x6f, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x46, 0x0a, 0x0f, - 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x62, - 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x48, 0x00, 0x52, 0x0f, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x1d, 0x0a, - 0x1b, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x46, - 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x14, 0x0a, 0x12, - 0x48, 0x61, 0x73, 0x4e, 0x6f, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x22, 0x16, 0x0a, 0x14, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, - 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xd9, 0x02, 0x0a, 0x09, 0x4d, - 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x68, 0x0a, 0x1c, 0x6e, 0x6f, 0x41, 0x63, - 0x74, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x46, 0x6f, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, - 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4b, 0x65, - 0x79, 0x46, 0x6f, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x48, 0x00, 0x52, 0x1c, 0x6e, 0x6f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4b, 0x65, - 0x79, 0x46, 0x6f, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x12, 0x43, 0x0a, 0x0e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x72, 0x70, - 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x55, 0x0a, 0x14, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x4c, 0x6f, 0x67, 0x6f, 0x75, - 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x14, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x3d, - 0x0a, 0x0c, 0x61, 0x70, 0x69, 0x43, 0x65, 0x72, 0x74, 0x49, 0x73, 0x73, 0x75, 0x65, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x70, 0x69, 0x43, - 0x65, 0x72, 0x74, 0x49, 0x73, 0x73, 0x75, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, - 0x0c, 0x61, 0x70, 0x69, 0x43, 0x65, 0x72, 0x74, 0x49, 0x73, 0x73, 0x75, 0x65, 0x42, 0x07, 0x0a, - 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x34, 0x0a, 0x1c, 0x4e, 0x6f, 0x41, 0x63, 0x74, 0x69, - 0x76, 0x65, 0x4b, 0x65, 0x79, 0x46, 0x6f, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, - 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x2f, 0x0a, 0x13, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x35, 0x0a, - 0x19, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x4c, - 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x41, 0x70, 0x69, 0x43, 0x65, 0x72, 0x74, 0x49, - 0x73, 0x73, 0x75, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xb4, 0x05, 0x0a, 0x09, 0x55, 0x73, - 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x5e, 0x0a, 0x17, 0x74, 0x6f, 0x67, 0x67, 0x6c, - 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, - 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, - 0x54, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, - 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x17, - 0x74, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, - 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x49, 0x0a, 0x10, 0x75, 0x73, 0x65, 0x72, 0x44, - 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, - 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, - 0x52, 0x10, 0x75, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x65, 0x64, 0x12, 0x3a, 0x0a, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, - 0x73, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, - 0x00, 0x52, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x38, - 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, - 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x75, 0x73, 0x65, 0x72, - 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x53, 0x0a, 0x15, 0x75, 0x73, 0x65, 0x64, - 0x42, 0x79, 0x74, 0x65, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, - 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x15, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, - 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x50, 0x0a, - 0x14, 0x69, 0x6d, 0x61, 0x70, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x72, - 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, - 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x14, 0x69, 0x6d, 0x61, 0x70, 0x4c, - 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, - 0x44, 0x0a, 0x10, 0x73, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x48, 0x00, 0x52, 0x10, 0x73, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, - 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, - 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, 0x73, 0x79, 0x6e, - 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x47, - 0x0a, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, - 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, - 0x22, 0x36, 0x0a, 0x1c, 0x54, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, - 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x22, 0x33, 0x0a, 0x15, 0x55, 0x73, 0x65, 0x72, - 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x2a, 0x0a, - 0x10, 0x55, 0x73, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x22, 0x4a, 0x0a, 0x0c, 0x55, 0x73, 0x65, - 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, - 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, - 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x4d, 0x0a, 0x15, 0x55, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, - 0x65, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, - 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, - 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x73, 0x65, 0x64, 0x42, - 0x79, 0x74, 0x65, 0x73, 0x22, 0x32, 0x0a, 0x14, 0x49, 0x6d, 0x61, 0x70, 0x4c, 0x6f, 0x67, 0x69, - 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, - 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x2a, 0x0a, 0x10, 0x53, 0x79, 0x6e, 0x63, - 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, - 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, - 0x65, 0x72, 0x49, 0x44, 0x22, 0x2b, 0x0a, 0x11, 0x53, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, - 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, - 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, - 0x44, 0x22, 0x87, 0x01, 0x0a, 0x11, 0x53, 0x79, 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, - 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, + 0x65, 0x22, 0x50, 0x0a, 0x12, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, + 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, - 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x01, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x65, - 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x4d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, - 0x65, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x4d, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x72, 0x65, 0x6d, - 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x4d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, - 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x4d, 0x73, 0x22, 0x38, 0x0a, 0x11, 0x47, - 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x12, 0x23, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x52, - 0x04, 0x63, 0x6f, 0x64, 0x65, 0x2a, 0x71, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, - 0x6c, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x50, 0x41, 0x4e, 0x49, 0x43, 0x10, 0x00, - 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x10, 0x01, 0x12, - 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x0c, - 0x0a, 0x08, 0x4c, 0x4f, 0x47, 0x5f, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, - 0x4c, 0x4f, 0x47, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, - 0x47, 0x5f, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, - 0x5f, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x06, 0x2a, 0x36, 0x0a, 0x09, 0x55, 0x73, 0x65, 0x72, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x49, 0x47, 0x4e, 0x45, 0x44, 0x5f, - 0x4f, 0x55, 0x54, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x4f, 0x43, 0x4b, 0x45, 0x44, 0x10, - 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, - 0x2a, 0xa2, 0x01, 0x0a, 0x0e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x55, 0x53, 0x45, 0x52, 0x4e, 0x41, 0x4d, 0x45, 0x5f, - 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, - 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x52, 0x45, 0x45, 0x5f, 0x55, 0x53, 0x45, 0x52, 0x10, 0x01, 0x12, - 0x14, 0x0a, 0x10, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x52, - 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x46, 0x41, 0x5f, 0x45, 0x52, 0x52, - 0x4f, 0x52, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x46, 0x41, 0x5f, 0x41, 0x42, 0x4f, 0x52, - 0x54, 0x10, 0x04, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x57, 0x4f, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x57, - 0x4f, 0x52, 0x44, 0x53, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x12, 0x17, 0x0a, 0x13, - 0x54, 0x57, 0x4f, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x53, 0x5f, 0x41, 0x42, - 0x4f, 0x52, 0x54, 0x10, 0x06, 0x2a, 0x5b, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x44, 0x41, - 0x54, 0x45, 0x5f, 0x4d, 0x41, 0x4e, 0x55, 0x41, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, - 0x00, 0x12, 0x16, 0x0a, 0x12, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x43, - 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x44, - 0x41, 0x54, 0x45, 0x5f, 0x53, 0x49, 0x4c, 0x45, 0x4e, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, - 0x10, 0x02, 0x2a, 0x6b, 0x0a, 0x12, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x1c, 0x44, 0x49, 0x53, 0x4b, - 0x5f, 0x43, 0x41, 0x43, 0x48, 0x45, 0x5f, 0x55, 0x4e, 0x41, 0x56, 0x41, 0x49, 0x4c, 0x41, 0x42, - 0x4c, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x41, - 0x4e, 0x54, 0x5f, 0x4d, 0x4f, 0x56, 0x45, 0x5f, 0x44, 0x49, 0x53, 0x4b, 0x5f, 0x43, 0x41, 0x43, - 0x48, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x44, 0x49, - 0x53, 0x4b, 0x5f, 0x46, 0x55, 0x4c, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x2a, - 0xdd, 0x01, 0x0a, 0x1b, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, - 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x1b, 0x0a, 0x17, 0x49, 0x4d, 0x41, 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x53, 0x54, 0x41, - 0x52, 0x54, 0x55, 0x50, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, - 0x53, 0x4d, 0x54, 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x55, - 0x50, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x49, 0x4d, 0x41, - 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, - 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x4d, 0x54, 0x50, 0x5f, 0x50, 0x4f, + 0x22, 0x0a, 0x0c, 0x77, 0x61, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4f, 0x75, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x77, 0x61, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, + 0x4f, 0x75, 0x74, 0x22, 0xb9, 0x04, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x12, 0x40, 0x0a, 0x0b, 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x61, + 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x61, 0x64, + 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, + 0x52, 0x65, 0x61, 0x64, 0x79, 0x12, 0x58, 0x0a, 0x13, 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, + 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, + 0x64, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x13, 0x6d, 0x61, 0x6e, 0x75, + 0x61, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x12, + 0x2e, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x63, + 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, + 0x53, 0x0a, 0x13, 0x73, 0x69, 0x6c, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x48, 0x00, 0x52, + 0x13, 0x73, 0x69, 0x6c, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, + 0x65, 0x64, 0x65, 0x64, 0x12, 0x47, 0x0a, 0x0f, 0x69, 0x73, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x73, 0x4c, 0x61, 0x74, + 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x0f, 0x69, 0x73, + 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x41, 0x0a, + 0x0d, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x48, + 0x00, 0x52, 0x0d, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, + 0x12, 0x44, 0x0a, 0x0e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, + 0x3d, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x15, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x32, + 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, + 0x61, 0x64, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x22, 0x20, 0x0a, 0x1e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x75, + 0x61, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x22, 0x2c, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x6f, + 0x72, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x22, 0x1b, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x6c, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x22, + 0x17, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x73, 0x4c, 0x61, 0x74, 0x65, 0x73, + 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x15, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x22, + 0x16, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x22, 0xeb, 0x01, 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x6b, + 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x31, 0x0a, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x43, 0x0a, + 0x0b, 0x70, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, + 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x70, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x64, 0x12, 0x58, 0x0a, 0x12, 0x70, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, + 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, + 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x70, 0x61, 0x74, 0x68, 0x43, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x43, 0x0a, 0x13, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, + 0x68, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2f, 0x0a, 0x19, 0x44, 0x69, + 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x22, 0x0a, 0x20, 0x44, + 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, + 0xbf, 0x02, 0x0a, 0x17, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, + 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3a, 0x0a, 0x05, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x64, 0x0a, 0x19, 0x6d, 0x61, 0x69, 0x6c, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x48, 0x00, 0x52, 0x19, 0x6d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, + 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x79, 0x0a, + 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, + 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, + 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x22, 0x55, 0x0a, 0x1c, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, + 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x35, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x21, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x54, 0x0a, 0x1e, 0x4d, 0x61, 0x69, 0x6c, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x43, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, + 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, 0x53, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x73, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x27, + 0x0a, 0x25, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, + 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xff, 0x01, 0x0a, 0x0d, 0x4b, 0x65, 0x79, 0x63, + 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x5b, 0x0a, 0x16, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x46, + 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x16, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x46, 0x69, + 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x40, 0x0a, 0x0d, 0x68, 0x61, 0x73, 0x4e, 0x6f, 0x4b, + 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x61, 0x73, 0x4e, 0x6f, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, + 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x68, 0x61, 0x73, 0x4e, 0x6f, + 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x46, 0x0a, 0x0f, 0x72, 0x65, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, + 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, + 0x0f, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x1d, 0x0a, 0x1b, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x14, 0x0a, 0x12, 0x48, 0x61, 0x73, 0x4e, + 0x6f, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x16, + 0x0a, 0x14, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xd9, 0x02, 0x0a, 0x09, 0x4d, 0x61, 0x69, 0x6c, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x12, 0x68, 0x0a, 0x1c, 0x6e, 0x6f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, + 0x4b, 0x65, 0x79, 0x46, 0x6f, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x4e, 0x6f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x46, 0x6f, 0x72, + 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x1c, 0x6e, 0x6f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x46, 0x6f, 0x72, + 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x43, + 0x0a, 0x0e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x0e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x64, 0x12, 0x55, 0x0a, 0x14, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x64, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x14, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x64, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x3d, 0x0a, 0x0c, 0x61, 0x70, + 0x69, 0x43, 0x65, 0x72, 0x74, 0x49, 0x73, 0x73, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x70, 0x69, 0x43, 0x65, 0x72, 0x74, 0x49, + 0x73, 0x73, 0x75, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x61, 0x70, 0x69, + 0x43, 0x65, 0x72, 0x74, 0x49, 0x73, 0x73, 0x75, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x22, 0x34, 0x0a, 0x1c, 0x4e, 0x6f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4b, 0x65, + 0x79, 0x46, 0x6f, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x2f, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, + 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x35, 0x0a, 0x19, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x4c, 0x6f, 0x67, 0x6f, 0x75, + 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x22, 0x13, 0x0a, 0x11, 0x41, 0x70, 0x69, 0x43, 0x65, 0x72, 0x74, 0x49, 0x73, 0x73, 0x75, 0x65, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xb4, 0x05, 0x0a, 0x09, 0x55, 0x73, 0x65, 0x72, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x5e, 0x0a, 0x17, 0x74, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x53, 0x70, 0x6c, + 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x67, 0x67, + 0x6c, 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x17, 0x74, 0x6f, 0x67, 0x67, + 0x6c, 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x12, 0x49, 0x0a, 0x10, 0x75, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x10, 0x75, 0x73, + 0x65, 0x72, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x3a, + 0x0a, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x75, + 0x73, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x0c, 0x75, 0x73, + 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x12, 0x53, 0x0a, 0x15, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, + 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x64, 0x42, + 0x79, 0x74, 0x65, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x48, 0x00, 0x52, 0x15, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x50, 0x0a, 0x14, 0x69, 0x6d, 0x61, + 0x70, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, + 0x6d, 0x61, 0x70, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x14, 0x69, 0x6d, 0x61, 0x70, 0x4c, 0x6f, 0x67, 0x69, 0x6e, + 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x44, 0x0a, 0x10, 0x73, + 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, + 0x63, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, + 0x10, 0x73, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x47, 0x0a, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, + 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, + 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x11, 0x73, 0x79, + 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, + 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x36, 0x0a, 0x1c, + 0x54, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, + 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x44, 0x22, 0x33, 0x0a, 0x15, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, + 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x2a, 0x0a, 0x10, 0x55, 0x73, 0x65, + 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, + 0x73, 0x65, 0x72, 0x49, 0x44, 0x22, 0x4a, 0x0a, 0x0c, 0x55, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x22, 0x0a, + 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x22, 0x4d, 0x0a, 0x15, 0x55, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x43, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, + 0x49, 0x44, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, + 0x22, 0x32, 0x0a, 0x14, 0x49, 0x6d, 0x61, 0x70, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, + 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, + 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x2a, 0x0a, 0x10, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x72, + 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, + 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, + 0x22, 0x2b, 0x0a, 0x11, 0x53, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x22, 0x87, 0x01, + 0x0a, 0x11, 0x53, 0x79, 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x1a, 0x0a, 0x08, 0x70, + 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x70, + 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x6c, 0x61, 0x70, 0x73, + 0x65, 0x64, 0x4d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x65, 0x6c, 0x61, 0x70, + 0x73, 0x65, 0x64, 0x4d, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, + 0x6e, 0x67, 0x4d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x61, + 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x4d, 0x73, 0x22, 0x38, 0x0a, 0x11, 0x47, 0x65, 0x6e, 0x65, 0x72, + 0x69, 0x63, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x04, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, + 0x65, 0x2a, 0x71, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x0d, 0x0a, + 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x50, 0x41, 0x4e, 0x49, 0x43, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, + 0x4c, 0x4f, 0x47, 0x5f, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x4c, + 0x4f, 0x47, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x4f, + 0x47, 0x5f, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x4f, 0x47, 0x5f, + 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x44, 0x45, + 0x42, 0x55, 0x47, 0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x54, 0x52, 0x41, + 0x43, 0x45, 0x10, 0x06, 0x2a, 0x36, 0x0a, 0x09, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x49, 0x47, 0x4e, 0x45, 0x44, 0x5f, 0x4f, 0x55, 0x54, 0x10, + 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x4f, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, + 0x09, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, 0x2a, 0xa2, 0x01, 0x0a, + 0x0e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x1b, 0x0a, 0x17, 0x55, 0x53, 0x45, 0x52, 0x4e, 0x41, 0x4d, 0x45, 0x5f, 0x50, 0x41, 0x53, 0x53, + 0x57, 0x4f, 0x52, 0x44, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, + 0x46, 0x52, 0x45, 0x45, 0x5f, 0x55, 0x53, 0x45, 0x52, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x43, + 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, + 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x46, 0x41, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, + 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x46, 0x41, 0x5f, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x10, 0x04, 0x12, + 0x17, 0x0a, 0x13, 0x54, 0x57, 0x4f, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x53, + 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x57, 0x4f, 0x5f, + 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x53, 0x5f, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x10, + 0x06, 0x2a, 0x5b, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x4d, + 0x41, 0x4e, 0x55, 0x41, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x16, 0x0a, + 0x12, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x45, 0x52, + 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, + 0x53, 0x49, 0x4c, 0x45, 0x4e, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x2a, 0x6b, + 0x0a, 0x12, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x1c, 0x44, 0x49, 0x53, 0x4b, 0x5f, 0x43, 0x41, 0x43, + 0x48, 0x45, 0x5f, 0x55, 0x4e, 0x41, 0x56, 0x41, 0x49, 0x4c, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x45, + 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x41, 0x4e, 0x54, 0x5f, 0x4d, + 0x4f, 0x56, 0x45, 0x5f, 0x44, 0x49, 0x53, 0x4b, 0x5f, 0x43, 0x41, 0x43, 0x48, 0x45, 0x5f, 0x45, + 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x44, 0x49, 0x53, 0x4b, 0x5f, 0x46, + 0x55, 0x4c, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x2a, 0xdd, 0x01, 0x0a, 0x1b, + 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, + 0x67, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x49, + 0x4d, 0x41, 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x55, 0x50, + 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x4d, 0x54, 0x50, + 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x55, 0x50, 0x5f, 0x45, 0x52, + 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x49, 0x4d, 0x41, 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, - 0x03, 0x12, 0x25, 0x0a, 0x21, 0x49, 0x4d, 0x41, 0x50, 0x5f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, - 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, - 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x25, 0x0a, 0x21, 0x53, 0x4d, 0x54, 0x50, - 0x5f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x4f, 0x44, 0x45, - 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x2a, - 0x53, 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x11, 0x0a, 0x0d, - 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, - 0x19, 0x0a, 0x15, 0x54, 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, 0xfc, 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, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, - 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x41, 0x64, - 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, - 0x2e, 0x41, 0x64, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 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, 0x3a, 0x0a, 0x08, 0x47, - 0x75, 0x69, 0x52, 0x65, 0x61, 0x64, 0x79, 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, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x75, 0x69, 0x52, 0x65, 0x61, 0x64, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x51, 0x75, 0x69, 0x74, 0x12, + 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x4d, 0x54, 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x43, + 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x25, 0x0a, + 0x21, 0x49, 0x4d, 0x41, 0x50, 0x5f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, + 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, + 0x4f, 0x52, 0x10, 0x04, 0x12, 0x25, 0x0a, 0x21, 0x53, 0x4d, 0x54, 0x50, 0x5f, 0x43, 0x4f, 0x4e, + 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x43, 0x48, 0x41, + 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x2a, 0x53, 0x0a, 0x09, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x55, 0x4e, 0x4b, 0x4e, + 0x4f, 0x57, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x54, + 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, 0xfc, 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, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x4c, 0x6f, 0x67, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, + 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 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, 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, - 0x39, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 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, 0x43, 0x0a, 0x0d, 0x53, 0x68, - 0x6f, 0x77, 0x4f, 0x6e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x16, 0x2e, 0x67, 0x6f, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3a, 0x0a, 0x08, 0x47, 0x75, 0x69, 0x52, 0x65, + 0x61, 0x64, 0x79, 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, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x75, 0x69, 0x52, 0x65, 0x61, 0x64, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x51, 0x75, 0x69, 0x74, 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, - 0x46, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x73, 0x74, 0x61, 0x72, - 0x74, 0x4f, 0x6e, 0x12, 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, 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, 0x43, 0x0a, 0x0d, 0x49, 0x73, 0x41, 0x75, 0x74, - 0x6f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 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, 0x39, 0x0a, 0x07, 0x52, + 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 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, 0x43, 0x0a, 0x0d, 0x53, 0x68, 0x6f, 0x77, 0x4f, 0x6e, + 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 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, 0x46, 0x0a, 0x10, 0x53, + 0x65, 0x74, 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4f, 0x6e, 0x12, + 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, 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, 0x43, 0x0a, 0x0d, 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x4f, 0x6e, 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, 0x46, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x49, + 0x73, 0x42, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 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, 0x1a, 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, 0x46, 0x0a, 0x10, - 0x53, 0x65, 0x74, 0x49, 0x73, 0x42, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x12, 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, 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, 0x43, 0x0a, 0x0d, 0x49, 0x73, 0x42, 0x65, 0x74, 0x61, 0x45, 0x6e, - 0x61, 0x62, 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, 0x49, 0x0a, 0x13, 0x53, 0x65, 0x74, - 0x49, 0x73, 0x41, 0x6c, 0x6c, 0x4d, 0x61, 0x69, 0x6c, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, - 0x12, 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, 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, 0x46, 0x0a, 0x10, 0x49, 0x73, 0x41, 0x6c, 0x6c, 0x4d, 0x61, 0x69, - 0x6c, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 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, 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, 0x4c, 0x0a, 0x16, - 0x53, 0x65, 0x74, 0x49, 0x73, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x44, 0x69, - 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 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, 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, 0x13, 0x49, 0x73, - 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, + 0x12, 0x43, 0x0a, 0x0d, 0x49, 0x73, 0x42, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x61, 0x62, 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, 0x3c, 0x0a, 0x04, 0x47, 0x6f, 0x4f, 0x73, 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, 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, 0x12, 0x3e, 0x0a, 0x0c, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, - 0x73, 0x65, 0x74, 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, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 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, 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, 0x12, 0x40, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x73, 0x50, 0x61, 0x74, 0x68, - 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, 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, 0x12, 0x43, 0x0a, 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, - 0x65, 0x50, 0x61, 0x74, 0x68, 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, 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, 0x12, 0x4c, 0x0a, 0x14, 0x52, - 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x73, 0x50, 0x61, 0x67, 0x65, 0x4c, - 0x69, 0x6e, 0x6b, 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, 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, 0x12, 0x4e, 0x0a, 0x16, 0x44, 0x65, 0x70, - 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x4c, - 0x69, 0x6e, 0x6b, 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, 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, 0x12, 0x47, 0x0a, 0x0f, 0x4c, 0x61, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 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, 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, 0x12, 0x4a, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x53, 0x63, - 0x68, 0x65, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 0x65, 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, 0x47, - 0x0a, 0x0f, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x4e, 0x61, 0x6d, - 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, 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, 0x12, 0x4a, 0x0a, 0x12, 0x43, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x74, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 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, 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, 0x12, 0x3b, 0x0a, 0x09, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, - 0x12, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, - 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x49, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x49, 0x73, 0x41, 0x6c, + 0x6c, 0x4d, 0x61, 0x69, 0x6c, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x12, 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, 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, 0x45, 0x0a, 0x0d, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, - 0x72, 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, 0x49, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x4d, 0x61, - 0x69, 0x6e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 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, 0x33, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x12, 0x2e, 0x67, 0x72, - 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 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, 0x36, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x69, 0x6e, - 0x32, 0x46, 0x41, 0x12, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, - 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, - 0x3d, 0x0a, 0x0f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x32, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, - 0x64, 0x73, 0x12, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 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, 0x3d, - 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x41, 0x62, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, 0x67, - 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x41, 0x62, 0x6f, 0x72, 0x74, 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, 0x3d, 0x0a, - 0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x55, 0x70, 0x64, 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, 0x3f, 0x0a, 0x0d, - 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, + 0x12, 0x46, 0x0a, 0x10, 0x49, 0x73, 0x41, 0x6c, 0x6c, 0x4d, 0x61, 0x69, 0x6c, 0x56, 0x69, 0x73, + 0x69, 0x62, 0x6c, 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, 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, 0x4c, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x49, + 0x73, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x12, 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, 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, 0x13, 0x49, 0x73, 0x54, 0x65, 0x6c, 0x65, + 0x6d, 0x65, 0x74, 0x72, 0x79, 0x44, 0x69, 0x73, 0x61, 0x62, 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, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4c, 0x0a, - 0x16, 0x53, 0x65, 0x74, 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x4f, 0x6e, 0x12, 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, 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, 0x13, 0x49, - 0x73, 0x41, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x4f, 0x6e, 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, 0x45, 0x0a, 0x0d, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, - 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 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, - 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, 0x12, 0x48, 0x0a, - 0x10, 0x53, 0x65, 0x74, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, - 0x68, 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, 0x45, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x49, 0x73, - 0x44, 0x6f, 0x48, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 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, 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, 0x0c, 0x49, 0x73, 0x44, 0x6f, 0x48, 0x45, 0x6e, 0x61, 0x62, 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, 0x44, 0x0a, 0x12, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 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, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, - 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x47, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x4d, - 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, - 0x73, 0x12, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, - 0x70, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 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, 0x40, 0x0a, 0x08, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 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, 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, 0x12, 0x45, 0x0a, 0x0a, 0x49, 0x73, 0x50, 0x6f, 0x72, 0x74, 0x46, 0x72, 0x65, - 0x65, 0x12, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 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, 0x4e, 0x0a, 0x12, 0x41, 0x76, - 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, - 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, 0x20, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, - 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, - 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x53, 0x65, - 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, - 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, 0x47, 0x0a, 0x0f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, - 0x74, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 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, 0x3c, 0x0a, 0x04, 0x47, 0x6f, 0x4f, 0x73, 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, 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, 0x12, - 0x3d, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 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, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, - 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, - 0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 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, 0x0a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, - 0x73, 0x65, 0x72, 0x12, 0x46, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x70, - 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, - 0x73, 0x65, 0x72, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 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, 0x55, 0x0a, 0x18, 0x53, - 0x65, 0x6e, 0x64, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x73, 0x65, 0x72, 0x46, - 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x21, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, - 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x46, 0x65, 0x65, 0x64, 0x62, - 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x3e, 0x0a, 0x0c, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x73, 0x65, 0x74, 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, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 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, 0x42, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x55, 0x73, 0x65, 0x72, - 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, 0x42, 0x0a, 0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, - 0x55, 0x73, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x74, 0x79, 0x1a, 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, + 0x12, 0x40, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x73, 0x50, 0x61, 0x74, 0x68, 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, 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, 0x12, 0x43, 0x0a, 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x50, 0x61, 0x74, + 0x68, 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, 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, 0x12, 0x4c, 0x0a, 0x14, 0x52, 0x65, 0x6c, 0x65, 0x61, + 0x73, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x73, 0x50, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 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, 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, 0x12, 0x4e, 0x0a, 0x16, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, + 0x6e, 0x63, 0x79, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x4c, 0x69, 0x6e, 0x6b, 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, 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, 0x12, 0x47, 0x0a, 0x0f, 0x4c, 0x61, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x50, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 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, 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, 0x12, 0x4a, + 0x0a, 0x12, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, + 0x4e, 0x61, 0x6d, 0x65, 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, 0x51, 0x0a, 0x16, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x65, - 0x4d, 0x61, 0x69, 0x6c, 0x12, 0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 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, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x47, 0x0a, 0x0f, 0x43, 0x6f, + 0x6c, 0x6f, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 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, 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, 0x12, 0x4a, 0x0a, 0x12, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x45, 0x6d, + 0x61, 0x69, 0x6c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 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, 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, 0x48, 0x0a, 0x10, - 0x4b, 0x42, 0x41, 0x72, 0x74, 0x69, 0x63, 0x6c, 0x65, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x65, 0x64, + 0x79, 0x1a, 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, 0x12, + 0x3b, 0x0a, 0x09, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x12, 0x16, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 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, 0x45, 0x0a, 0x0d, + 0x46, 0x6f, 0x72, 0x63, 0x65, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x72, 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, 0x49, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x4d, 0x61, 0x69, 0x6e, 0x45, 0x78, + 0x65, 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 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, 0x33, + 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, + 0x6f, 0x67, 0x69, 0x6e, 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, 0x36, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x32, 0x46, 0x41, 0x12, + 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 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, 0x3d, 0x0a, 0x0f, 0x4c, + 0x6f, 0x67, 0x69, 0x6e, 0x32, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x12, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 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, 0x3d, 0x0a, 0x0a, 0x4c, 0x6f, + 0x67, 0x69, 0x6e, 0x41, 0x62, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, + 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x41, 0x62, 0x6f, 0x72, 0x74, 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, 0x3d, 0x0a, 0x0b, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x55, 0x70, 0x64, 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, 0x3f, 0x0a, 0x0d, 0x49, 0x6e, 0x73, 0x74, + 0x61, 0x6c, 0x6c, 0x55, 0x70, 0x64, 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, 0x4c, 0x0a, 0x16, 0x53, 0x65, 0x74, + 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x4f, 0x6e, 0x12, 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, 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, 0x13, 0x49, 0x73, 0x41, 0x75, 0x74, + 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4f, 0x6e, 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, 0x45, 0x0a, 0x0d, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, + 0x61, 0x74, 0x68, 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, 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, 0x12, 0x48, 0x0a, 0x10, 0x53, 0x65, 0x74, + 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 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, 0x45, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x49, 0x73, 0x44, 0x6f, 0x48, 0x45, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 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, 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, 0x0c, 0x49, 0x73, + 0x44, 0x6f, 0x48, 0x45, 0x6e, 0x61, 0x62, 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, 0x44, + 0x0a, 0x12, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x73, 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, + 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, 0x53, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x73, 0x12, 0x47, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x4d, 0x61, 0x69, 0x6c, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x16, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, 0x53, 0x65, 0x74, + 0x74, 0x69, 0x6e, 0x67, 0x73, 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, 0x40, 0x0a, + 0x08, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 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, 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, 0x12, + 0x45, 0x0a, 0x0a, 0x49, 0x73, 0x50, 0x6f, 0x72, 0x74, 0x46, 0x72, 0x65, 0x65, 0x12, 0x1b, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 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, 0x4e, 0x0a, 0x12, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, + 0x62, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 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, 0x20, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x76, 0x61, 0x69, + 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x43, 0x75, 0x72, + 0x72, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 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, 0x47, 0x0a, 0x0f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, + 0x63, 0x68, 0x61, 0x69, 0x6e, 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, 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, 0x12, 0x3d, 0x0a, 0x0b, 0x47, + 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 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, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x47, 0x65, + 0x74, 0x55, 0x73, 0x65, 0x72, 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, 0x0a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x12, + 0x46, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, + 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x53, + 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 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, 0x55, 0x0a, 0x18, 0x53, 0x65, 0x6e, 0x64, 0x42, + 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x73, 0x65, 0x72, 0x46, 0x65, 0x65, 0x64, 0x62, + 0x61, 0x63, 0x6b, 0x12, 0x21, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x42, + 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 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, 0x0a, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x55, 0x73, 0x65, 0x72, 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, 0x42, 0x0a, 0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4f, 0x0a, 0x19, 0x49, 0x73, 0x54, 0x4c, 0x53, 0x43, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, - 0x6c, 0x65, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, - 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x47, 0x0a, 0x15, 0x49, 0x6e, 0x73, 0x74, 0x61, - 0x6c, 0x6c, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x12, 0x4d, 0x0a, 0x15, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, - 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 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, 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, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x51, 0x0a, 0x16, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x4d, 0x61, 0x69, 0x6c, + 0x12, 0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 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, 0x48, 0x0a, 0x10, 0x4b, 0x42, 0x41, 0x72, + 0x74, 0x69, 0x63, 0x6c, 0x65, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x1c, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x12, 0x4f, 0x0a, 0x19, 0x49, 0x73, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x12, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x47, 0x0a, 0x15, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x54, 0x4c, + 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4d, 0x0a, 0x15, + 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 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, 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 ( diff --git a/internal/frontend/grpc/bridge.proto b/internal/frontend/grpc/bridge.proto index 23df91b5..4199de5b 100644 --- a/internal/frontend/grpc/bridge.proto +++ b/internal/frontend/grpc/bridge.proto @@ -319,7 +319,9 @@ message LoginTfaRequestedEvent { string username = 1; } -message LoginTwoPasswordsRequestedEvent {} +message LoginTwoPasswordsRequestedEvent { + string username = 1; +} message LoginFinishedEvent { string userID = 1; diff --git a/internal/frontend/grpc/event_factory.go b/internal/frontend/grpc/event_factory.go index 13310887..4bdb7899 100644 --- a/internal/frontend/grpc/event_factory.go +++ b/internal/frontend/grpc/event_factory.go @@ -69,8 +69,8 @@ func NewLoginTfaRequestedEvent(username string) *StreamEvent { return loginEvent(&LoginEvent{Event: &LoginEvent_TfaRequested{TfaRequested: &LoginTfaRequestedEvent{Username: username}}}) } -func NewLoginTwoPasswordsRequestedEvent() *StreamEvent { - return loginEvent(&LoginEvent{Event: &LoginEvent_TwoPasswordRequested{}}) +func NewLoginTwoPasswordsRequestedEvent(username string) *StreamEvent { + return loginEvent(&LoginEvent{Event: &LoginEvent_TwoPasswordRequested{TwoPasswordRequested: &LoginTwoPasswordsRequestedEvent{Username: username}}}) } func NewLoginFinishedEvent(userID string, wasSignedOut bool) *StreamEvent { diff --git a/internal/frontend/grpc/service_methods.go b/internal/frontend/grpc/service_methods.go index 2f8189a0..009856c6 100644 --- a/internal/frontend/grpc/service_methods.go +++ b/internal/frontend/grpc/service_methods.go @@ -424,7 +424,7 @@ func (s *Service) Login(_ context.Context, login *LoginRequest) (*emptypb.Empty, _ = s.SendEvent(NewLoginTfaRequestedEvent(login.Username)) case auth.PasswordMode == proton.TwoPasswordMode: - _ = s.SendEvent(NewLoginTwoPasswordsRequestedEvent()) + _ = s.SendEvent(NewLoginTwoPasswordsRequestedEvent(login.Username)) default: s.finishLogin() @@ -469,7 +469,7 @@ func (s *Service) Login2FA(_ context.Context, login *LoginRequest) (*emptypb.Emp } if s.auth.PasswordMode == proton.TwoPasswordMode { - _ = s.SendEvent(NewLoginTwoPasswordsRequestedEvent()) + _ = s.SendEvent(NewLoginTwoPasswordsRequestedEvent(login.Username)) return } diff --git a/internal/frontend/grpc/service_stream.go b/internal/frontend/grpc/service_stream.go index a7491dae..c87b34c5 100644 --- a/internal/frontend/grpc/service_stream.go +++ b/internal/frontend/grpc/service_stream.go @@ -126,7 +126,7 @@ func (s *Service) StartEventTest() error { // login NewLoginError(LoginErrorType_FREE_USER, "error"), NewLoginTfaRequestedEvent(dummyAddress), - NewLoginTwoPasswordsRequestedEvent(), + NewLoginTwoPasswordsRequestedEvent(dummyAddress), NewLoginFinishedEvent("userID", false), NewLoginAlreadyLoggedInEvent("userID"), From 272f9cf59bf5d39242b01a81dbcf92980c8be424 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Mon, 21 Aug 2023 17:46:06 +0200 Subject: [PATCH 40/93] feat(GODT-2772): new client selector design. --- .../bridge-gui/bridge-gui/Resources.qrc | 1 + .../bridge-gui/qml/Proton/Button.qml | 5 +- .../qml/SetupWizard/ClientConfigSelector.qml | 21 +++---- .../qml/SetupWizard/ClientListItem.qml | 63 ++++++++++--------- .../bridge-gui/qml/SetupWizard/HelpButton.qml | 1 + .../bridge-gui/qml/SetupWizard/LeftPane.qml | 37 ++++++----- .../qml/SetupWizard/SetupWizard.qml | 8 --- .../qml/icons/img-client-config-selector.svg | 47 ++++++++++++++ 8 files changed, 113 insertions(+), 70 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/icons/img-client-config-selector.svg diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index db266b0d..5d1e4cc7 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -49,6 +49,7 @@ qml/icons/ic-success.svg qml/icons/ic-three-dots-vertical.svg qml/icons/ic-trash.svg + qml/icons/img-client-config-selector.svg qml/icons/img-mail-clients.svg qml/icons/img-mail-logo-wordmark-dark.svg qml/icons/img-mail-logo-wordmark.svg diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml index dd844c94..2c3f5422 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml @@ -30,6 +30,7 @@ T.Button { property alias secondary: control.flat property alias textHorizontalAlignment: label.horizontalAlignment property alias textVerticalAlignment: label.verticalAlignment + property bool secondaryIsOpaque: false; font: label.font horizontalPadding: 16 @@ -77,7 +78,7 @@ T.Button { if (control.loading) { return control.colorScheme.interaction_default_hover; } - return control.colorScheme.interaction_default; + return secondaryIsOpaque ? control.colorScheme.background_norm: control.colorScheme.interaction_default; } } else { if (primary) { @@ -103,7 +104,7 @@ T.Button { if (control.loading) { return control.colorScheme.interaction_default_hover; } - return control.colorScheme.interaction_default; + return secondaryIsOpaque ? control.colorScheme.background_norm : control.colorScheme.interaction_default; } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml index 28db59c9..2de2c47a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml @@ -27,18 +27,18 @@ Item { ColumnLayout { anchors.left: parent.left anchors.right: parent.right - anchors.top: parent.top - spacing: 0 + anchors.verticalCenter: parent.verticalCenter + spacing: 16 Label { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true + Layout.topMargin: 16 colorScheme: wizard.colorScheme - text: qsTr("Select your email application") - type: Label.LabelType.Heading - } - Item { - Layout.preferredHeight: 72 + horizontalAlignment: Qt.AlignHCenter + text: qsTr("Select your email client") + type: Label.LabelType.Title + wrapMode: Text.WordWrap } ClientListItem { Layout.fillWidth: true @@ -86,14 +86,13 @@ Item { wizard.showClientParams(); } } - Item { - Layout.preferredHeight: 72 - } Button { Layout.fillWidth: true + Layout.topMargin: 20 colorScheme: wizard.colorScheme secondary: true - text: qsTr("Cancel") + secondaryIsOpaque: true + text: qsTr("Setup later") onClicked: { root.wizard.closeWizard(); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientListItem.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientListItem.qml index da92fcac..5422cdfe 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientListItem.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientListItem.qml @@ -17,7 +17,7 @@ import QtQuick.Controls import QtQuick.Controls.impl import Proton -Item { +Rectangle { id: root property ColorScheme colorScheme @@ -26,41 +26,44 @@ Item { signal clicked - implicitHeight: clientRow.height - - ColumnLayout { - id: clientRow - anchors.left: parent.left - anchors.right: parent.right - spacing: 0 - - RowLayout { - Layout.bottomMargin: 12 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.topMargin: 12 - - ColorImage { - height: 36 - source: iconSource - sourceSize.height: 36 - } - Label { - Layout.leftMargin: 12 - colorScheme: root.colorScheme - text: root.text - type: Label.LabelType.Body - } + border.color: colorScheme.border_norm + border.width: 1 + color: { + if (mouseArea.pressed) { + return colorScheme.interaction_default_active; } - Rectangle { + if (mouseArea.containsMouse) { + return colorScheme.interaction_default_hover + } + return colorScheme.background_norm; + } + height: 68 + radius: 12 + + RowLayout { + anchors.fill: parent + anchors.margins: 16 + + ColorImage { + height: 36 + source: iconSource + sourceSize.height: 36 + } + Label { Layout.fillWidth: true - Layout.preferredHeight: 1 - color: root.colorScheme.border_weak + Layout.leftMargin: 12 + colorScheme: root.colorScheme + horizontalAlignment: Text.AlignLeft + text: root.text + type: Label.LabelType.Body + verticalAlignment: Text.AlignVCenter } } MouseArea { + id: mouseArea + acceptedButtons: Qt.LeftButton anchors.fill: parent - cursorShape: Qt.PointingHandCursor + hoverEnabled: true onClicked: { root.clicked(); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml index 1d9ac95c..47d4d357 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml @@ -31,6 +31,7 @@ Button { icon.source: "/qml/icons/ic-question-circle.svg" icon.height: 24 icon.width: 24 + icon.color: wizard.colorScheme.text_weak verticalPadding: 0 onClicked: { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 05e0ece9..303498f8 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -22,27 +22,28 @@ Item { id: root property var wizard + property int iconHeight + property int iconWidth + property string iconSource function showClientConfigCommon() { const clientName = wizard.clientName(); titleLabel.text = qsTr("Configure %1").arg(clientName); descriptionLabel.text = qsTr("We will now guide you through the process of setting up your Proton account in %1.").arg(clientName); icon.source = wizard.clientIconSource(); - icon.sourceSize.height = 128; - icon.sourceSize.width = 128; + icon.sourceSize.height = 264; + icon.sourceSize.width = 263; Layout.preferredHeight = 72; Layout.preferredWidth = 72; } - function showClientConfigWarning() { - showClientConfigCommon(); - linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why can't I use my Proton password in my email client?")); - } function showClientSelector() { - titleLabel.text = qsTr("Configure your email client"); + titleLabel.text = ""; descriptionLabel.text = qsTr("Bridge is now connected to Proton, and has already started downloading your messages. Let’s now connect your email client to Bridge."); linkLabel1.clear(); linkLabel2.clear(); - icon.source = "/qml/icons/img-mail-clients.svg"; + iconSource = "/qml/icons/img-client-config-selector.svg"; + iconHeight = 222; + iconWidth = 264; } function showLogin() { showOnboarding() @@ -58,11 +59,9 @@ Item { descriptionLabel.text = qsTr("Bridge is the gateway between your Proton account and your email client. It runs in the background and encrypts and decrypts your messages seamlessly. "); linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why do I need Bridge?")); linkLabel2.clear(); - icon.Layout.preferredHeight = 148; - icon.Layout.preferredWidth = 265; - icon.source = "/qml/icons/img-welcome.svg"; - icon.sourceSize.height = 148; - icon.sourceSize.width = 265; + iconSource = "/qml/icons/img-welcome.svg" + iconHeight= 148; + iconWidth = 265; } Connections { @@ -84,12 +83,11 @@ Item { Image { id: icon Layout.alignment: Qt.AlignHCenter | Qt.AlignTop - Layout.preferredHeight: 72 - Layout.preferredWidth: 72 - fillMode: Image.PreserveAspectFit - source: "" - sourceSize.height: 72 - sourceSize.width: 72 + Layout.preferredHeight: iconHeight + Layout.preferredWidth: iconWidth + source: iconSource + sourceSize.height: iconHeight + sourceSize.width: iconWidth } Label { id: titleLabel @@ -100,6 +98,7 @@ Item { text: "" type: Label.LabelType.Heading wrapMode: Text.WordWrap + visible: text.length !== 0 } Label { id: descriptionLabel diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 5a6a1046..abfafd5c 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -182,16 +182,12 @@ Item { // rightContent stack index 0 Onboarding { - Layout.fillHeight: true - Layout.fillWidth: true wizard: root } // rightContent tack index 1 Login { id: login - Layout.fillHeight: true - Layout.fillWidth: true wizard: root onLoginAbort: { @@ -202,15 +198,11 @@ Item { // rightContent stack index 2 ClientConfigSelector { id: clientConfigSelector - Layout.fillHeight: true - Layout.fillWidth: true wizard: root } // rightContent stack index 3 ClientConfigAppleMail { id: clientConfigAppleMail - Layout.fillHeight: true - Layout.fillWidth: true wizard: root } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-client-config-selector.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-client-config-selector.svg new file mode 100644 index 00000000..5b107446 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-client-config-selector.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From a9e95f618b984e571856bd36fb1288c6c677d562 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Tue, 22 Aug 2023 10:50:02 +0200 Subject: [PATCH 41/93] feat(GODT-2772): tweaked client parameter screen. --- .../bridge-gui/bridge-gui/Resources.qrc | 1 + .../bridge-gui/qml/Configuration.qml | 4 +- .../bridge-gui/qml/ConfigurationItem.qml | 4 +- .../SetupWizard/ClientConfigParameters.qml | 110 +++++++++++++----- .../qml/icons/ic-warning-orange.svg | 11 ++ 5 files changed, 97 insertions(+), 33 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-warning-orange.svg diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index 5d1e4cc7..3fc9cc41 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -49,6 +49,7 @@ qml/icons/ic-success.svg qml/icons/ic-three-dots-vertical.svg qml/icons/ic-trash.svg + qml/icons/ic-warning-orange.svg qml/icons/img-client-config-selector.svg qml/icons/img-mail-clients.svg qml/icons/img-mail-logo-wordmark-dark.svg diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Configuration.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Configuration.qml index c162dcfc..5d63ef9e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Configuration.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Configuration.qml @@ -21,6 +21,7 @@ Rectangle { property int _margin: 24 property ColorScheme colorScheme + property bool highlightPassword property string hostname property string password property string port @@ -68,7 +69,8 @@ Rectangle { } ConfigurationItem { colorScheme: root.colorScheme - label: qsTr("Password") + label: highlightPassword ? qsTr("Use this password") : qsTr("Password") + labelColor: highlightPassword ? colorScheme.signal_warning_active : colorScheme.text_norm value: root.password } ConfigurationItem { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/ConfigurationItem.qml b/internal/frontend/bridge-gui/bridge-gui/qml/ConfigurationItem.qml index 55cf2634..dc52cf20 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/ConfigurationItem.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/ConfigurationItem.qml @@ -21,6 +21,7 @@ Item { property var colorScheme property string label + property string labelColor: root.colorScheme.text_norm property string value Layout.fillWidth: true @@ -35,9 +36,10 @@ Item { ColumnLayout { Label { + color: labelColor colorScheme: root.colorScheme text: root.label - type: Label.Body + type: Label.Body_semibold } TextEdit { id: valueText diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml index 039571de..46caf9cb 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml @@ -21,9 +21,11 @@ import ".." Rectangle { id: root + property ColorScheme colorScheme: wizard.colorScheme readonly property bool genericClient: SetupWizard.Client.Generic === wizard.client property var wizard + clip: true color: colorScheme.background_weak Item { @@ -31,15 +33,13 @@ Rectangle { anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top - width: 800 + width: 640 ColumnLayout { - anchors.bottomMargin: 96 anchors.left: parent.left anchors.right: parent.right - anchors.top: parent.top - anchors.topMargin: 32 - spacing: 0 + anchors.verticalCenter: parent.verticalCenter + spacing: 16 Label { Layout.alignment: Qt.AlignHCenter @@ -47,31 +47,84 @@ Rectangle { colorScheme: wizard.colorScheme horizontalAlignment: Text.AlignHCenter text: qsTr("Configure %1").arg(wizard.clientName()) - type: Label.LabelType.Heading + type: Label.LabelType.Title wrapMode: Text.WordWrap } - Label { - id: descriptionLabel - Layout.alignment: Qt.AlignHCenter + Rectangle { Layout.fillWidth: true - Layout.topMargin: 8 - color: colorScheme.text_weak - colorScheme: wizard.colorScheme - horizontalAlignment: Text.AlignHCenter - text: genericClient ? qsTr("Here are the IMAP and SMTP configuration parameters for your email client") : qsTr("Here are your email configuration parameters for %1. \nWe have prepared an easy to follow configuration guide to help you setup your account in %1.").arg(wizard.clientName()) - type: Label.LabelType.Body - wrapMode: Text.WordWrap + border.color: colorScheme.border_norm + border.width: 1 + color: "transparent" + height: childrenRect.height + 2 * 16 + radius: 12 + + RowLayout { + anchors.left: parent.left + anchors.margins: 16 + anchors.right: parent.right + anchors.top: parent.top + spacing: 16 + + Label { + Layout.fillHeight: true + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignLeft + text: (SetupWizard.Client.MicrosoftOutlook === wizard.client) ? qsTr("Are you unsure about your Outlook version or do you need assistance in configuring Outlook?") : qsTr("Do you need assistant is configuring %1?".arg(wizard.clientName())) + type: Label.LabelType.Body + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + } + Button { + colorScheme: root.colorScheme + text: qsTr("Open Guide") + } + } + } + Rectangle { + Layout.fillWidth: true + border.color: colorScheme.signal_warning + border.width: 1 + color: "transparent" + height: childrenRect.height + 2 * 16 + radius: 12 + + RowLayout { + anchors.left: parent.left + anchors.margins: 16 + anchors.right: parent.right + anchors.top: parent.top + spacing: 16 + + ColorImage { + id: image + height: 36 + source: "/qml/icons/ic-warning-orange.svg" + sourceSize.height: height + sourceSize.width: width + width: height + } + Label { + Layout.fillHeight: true + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignLeft + text: qsTr("Copy paste the provided configuration parameters. Use the password below (not your Proton password), when adding your Proton account to %1.".arg(wizard.clientName())) + type: Label.LabelType.Body + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + } + } } RowLayout { id: configuration - Layout.fillHeight: true Layout.fillWidth: true - Layout.topMargin: 32 - spacing: 64 + spacing: 32 Configuration { Layout.fillWidth: true colorScheme: wizard.colorScheme + highlightPassword: true hostname: Backend.hostname password: wizard.user ? wizard.user.password : "" port: Backend.imapPort.toString() @@ -82,6 +135,7 @@ Rectangle { Configuration { Layout.fillWidth: true colorScheme: wizard.colorScheme + highlightPassword: true hostname: Backend.hostname password: wizard.user ? wizard.user.password : "" port: Backend.smtpPort.toString() @@ -92,21 +146,15 @@ Rectangle { } Button { Layout.alignment: Qt.AlignHCenter - Layout.preferredWidth: 444 - Layout.topMargin: 32 - colorScheme: wizard.colorScheme - text: qsTr("Open configuration guide") - visible: !genericClient - } - Button { - Layout.alignment: Qt.AlignHCenter - Layout.preferredWidth: 444 - Layout.topMargin: 32 - colorScheme: wizard.colorScheme - text: qsTr("Done") + Layout.preferredWidth: 304 + colorScheme: root.colorScheme + secondary: true + secondaryIsOpaque: true + text: qsTr("Continue") onClicked: wizard.closeWizard() } } } } + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-warning-orange.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-warning-orange.svg new file mode 100644 index 00000000..9b96d79d --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-warning-orange.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + From 15c18189d35e207d4c79a8fe881fd1315ae2effa Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Tue, 22 Aug 2023 18:44:32 +0200 Subject: [PATCH 42/93] feat(GODT-2772): client config success screen. --- .../bridge-gui/bridge-gui/Resources.qrc | 2 + .../bridge-gui/qml/Proton/Button.qml | 1 + .../qml/SetupWizard/ClientConfigEnd.qml | 101 ++++++++++++++++++ .../SetupWizard/ClientConfigParameters.qml | 5 +- .../bridge-gui/qml/SetupWizard/Login.qml | 3 + .../qml/SetupWizard/SetupWizard.qml | 14 ++- .../bridge-gui/qml/icons/ic-external-link.svg | 3 +- .../qml/icons/img-client-config-success.svg | 33 ++++++ 8 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigEnd.qml create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/icons/img-client-config-success.svg diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index 3fc9cc41..509412c0 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -51,6 +51,7 @@ qml/icons/ic-trash.svg qml/icons/ic-warning-orange.svg qml/icons/img-client-config-selector.svg + qml/icons/img-client-config-success.svg qml/icons/img-mail-clients.svg qml/icons/img-mail-logo-wordmark-dark.svg qml/icons/img-mail-logo-wordmark.svg @@ -111,6 +112,7 @@ qml/SetupWizard/ClientListItem.qml qml/SetupWizard/LeftPane.qml qml/SetupWizard/ClientConfigAppleMail.qml + qml/SetupWizard/ClientConfigEnd.qml qml/SetupWizard/ClientConfigParameters.qml qml/SetupWizard/ClientConfigSelector.qml qml/SetupWizard/HelpButton.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml index 2c3f5422..f505bca1 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml @@ -136,6 +136,7 @@ T.Button { text: control.text type: labelType visible: !control.isIcon + verticalAlignment: Text.AlignVCenter } ColorImage { id: iconImage diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigEnd.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigEnd.qml new file mode 100644 index 00000000..02b80f6c --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigEnd.qml @@ -0,0 +1,101 @@ +// Copyright (c) 2023 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 . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.impl +import Proton + +Rectangle { + id: root + + property ColorScheme colorScheme: wizard.colorScheme + property var wizard + + clip: true + color: colorScheme.background_norm + + Item { + id: centeredContainer + anchors.bottom: parent.bottom + anchors.bottomMargin: 84 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 32 + clip: true + width: 364 + + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: 16 + + Image { + Layout.alignment: Qt.AlignHCenter + Layout.preferredHeight: sourceSize.height + Layout.preferredWidth: sourceSize.width + source: "/qml/icons/img-client-config-success.svg" + sourceSize.height: 104 + sourceSize.width: 190 + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Congratulations! You're all setup") + type: Label.LabelType.Title + wrapMode: Text.WordWrap + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + color: colorScheme.text_weak + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: wizard.address + type: Label.LabelType.Body + wrapMode: Text.WordWrap + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Your client has been configured. While complete synchronization might take some time, you can already send encrypted emails.") + type: Label.LabelType.Body + wrapMode: Text.WordWrap + } + Button { + Layout.fillWidth: true + colorScheme: root.colorScheme + text: qsTr("Done") + + onClicked: wizard.closeWizard() + } + } + } + Image { + id: mailLogoWithWordmark + anchors.bottom: parent.bottom + anchors.bottomMargin: 32 + anchors.horizontalCenter: parent.horizontalCenter + height: 36 + source: root.colorScheme.mail_logo_with_wordmark + sourceSize.height: height + sourceSize.width: width + width: 134 + } +} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml index 46caf9cb..14e10f94 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml @@ -63,7 +63,7 @@ Rectangle { anchors.margins: 16 anchors.right: parent.right anchors.top: parent.top - spacing: 16 + spacing: 8 Label { Layout.fillHeight: true @@ -77,6 +77,7 @@ Rectangle { } Button { colorScheme: root.colorScheme + icon.source: "/qml/icons/ic-external-link.svg" text: qsTr("Open Guide") } } @@ -152,7 +153,7 @@ Rectangle { secondaryIsOpaque: true text: qsTr("Continue") - onClicked: wizard.closeWizard() + onClicked: wizard.showClientConfigEnd() } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml index 5a10398d..35fd9a1e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml @@ -263,6 +263,7 @@ FocusScope { colorScheme: wizard.colorScheme enabled: !signInButton.loading secondary: true + secondaryIsOpaque: true text: qsTr("Cancel") onClicked: { @@ -369,6 +370,7 @@ FocusScope { colorScheme: wizard.colorScheme enabled: !twoFAButton.loading secondary: true + secondaryIsOpaque: true text: qsTr("Cancel") onClicked: { @@ -464,6 +466,7 @@ FocusScope { colorScheme: wizard.colorScheme enabled: !secondPasswordButton.loading secondary: true + secondaryIsOpaque: true text: qsTr("Cancel") onClicked: { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index abfafd5c..904a66e4 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -34,7 +34,8 @@ Item { } enum RootStack { TwoPanesView, - ClientConfigParameters + ClientConfigParameters, + ClientConfigEnd } property string address @@ -95,6 +96,9 @@ Item { function showClientParams() { rootStackLayout.currentIndex = SetupWizard.RootStack.ClientConfigParameters; } + function showClientConfigEnd() { + rootStackLayout.currentIndex = SetupWizard.RootStack.ClientConfigEnd; + } function showLogin(username = "") { rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; root.address = ""; @@ -216,6 +220,14 @@ Item { Layout.fillWidth: true wizard: root } + + // rootStackLayout index 2 + ClientConfigEnd { + id: clientConfigEnd + Layout.fillHeight: true + Layout.fillWidth: true + wizard: root + } } HelpButton { wizard: root diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-external-link.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-external-link.svg index e2abee05..f55efec2 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-external-link.svg +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-external-link.svg @@ -1,3 +1,4 @@ - + + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-client-config-success.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-client-config-success.svg new file mode 100644 index 00000000..1ba691e3 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-client-config-success.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 1203709ab9b5ef690c58e42672f2d932c4be00aa Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Wed, 23 Aug 2023 10:29:42 +0200 Subject: [PATCH 43/93] feat(GODT-2772): Apple Mail cert install page. --- .../bridge-gui/bridge-gui/Resources.qrc | 1 + .../qml/SetupWizard/ClientConfigAppleMail.qml | 148 +++++++++++------- .../SetupWizard/ClientConfigParameters.qml | 2 +- .../bridge-gui/qml/SetupWizard/LeftPane.qml | 60 ++++--- .../qml/SetupWizard/SetupWizard.qml | 29 ++-- .../qml/icons/img-macos-cert-screenshot.png | Bin 0 -> 9410 bytes 6 files changed, 151 insertions(+), 89 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/icons/img-macos-cert-screenshot.png diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index 509412c0..90488c98 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -52,6 +52,7 @@ qml/icons/ic-warning-orange.svg qml/icons/img-client-config-selector.svg qml/icons/img-client-config-success.svg + qml/icons/img-macos-cert-screenshot.png qml/icons/img-mail-clients.svg qml/icons/img-mail-logo-wordmark-dark.svg qml/icons/img-mail-logo-wordmark.svg diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml index 1cd093c5..61c30eb3 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml @@ -20,18 +20,23 @@ import Proton Item { id: root enum Screen { - CertificateInstall = 0, - ProfileInstall = 1 + CertificateInstall, + ProfileInstall } property var wizard - function showAutoConfig() { - certInstallButton.loading = false; + signal appleMailAutoconfigCertificateInstallPageShown + signal appleMailAutoconfigProfileInstallPageShow + + function showAutoconfig() { + certificateInstall.waitingForCert = false; if (Backend.isTLSCertificateInstalled()) { stack.currentIndex = ClientConfigAppleMail.Screen.ProfileInstall; + appleMailAutoconfigProfileInstallPageShow(); } else { stack.currentIndex = ClientConfigAppleMail.Screen.CertificateInstall; + appleMailAutoconfigCertificateInstallPageShown(); } } @@ -40,67 +45,102 @@ Item { anchors.fill: parent // stack index 0 - ColumnLayout { + Item { id: certificateInstall + + property bool waitingForCert: false + Layout.fillHeight: true Layout.fillWidth: true - Connections { - function onCertificateInstallCanceled() { - // Note: this will lead to an error message in the final version. - certInstallButton.loading = false; - console.error("Certificate installation was canceled"); - } - function onCertificateInstallFailed() { - // Note: this will lead to an error page later. - certInstallButton.loading = false; - console.error("Certificate installation failed"); - } - function onCertificateInstallSuccess() { - certInstallButton.loading = false; - console.error("Certificate installed successfully"); - stack.currentIndex = ClientConfigAppleMail.Screen.ProfileInstall; - } + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: 24 - target: Backend - } - Label { - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - colorScheme: wizard.colorScheme - horizontalAlignment: Text.AlignHCenter - text: "Certificate install placeholder" - type: Label.LabelType.Heading - wrapMode: Text.WordWrap - } - Button { - id: certInstallButton - Layout.fillWidth: true - Layout.topMargin: 48 - colorScheme: wizard.colorScheme - enabled: !loading - loading: false - text: "Install Certificate Placeholder" + Connections { + function onCertificateInstallCanceled() { + // Note: this will lead to an error message in the final version. + certificateInstall.waitingForCert = false; + console.error("Certificate installation was canceled"); + } + function onCertificateInstallFailed() { + // Note: this will lead to an error page later. + certificateInstall.waitingForCert = false; + console.error("Certificate installation failed"); + } + function onCertificateInstallSuccess() { + certificateInstall.waitingForCert = false; + console.error("Certificate installed successfully"); + root.showAutoconfig(); + } - onClicked: { - certInstallButton.loading = true; - Backend.installTLSCertificate(); + target: Backend } - } - Button { - Layout.fillWidth: true - Layout.topMargin: 32 - colorScheme: wizard.colorScheme - enabled: !certInstallButton.loading - secondary: true - text: qsTr("Cancel") + ColumnLayout { + Layout.fillWidth: true + spacing: 16 - onClicked: { - wizard.closeWizard(); + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: "Install the bridge certificate" + type: Label.LabelType.Title + wrapMode: Text.WordWrap + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + color: colorScheme.text_weak + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: "After clicking on the button below, a system pop-up will ask you for your credential, please enter your macOS user credentials (not your Proton account’s) and validate." + type: Label.LabelType.Body + wrapMode: Text.WordWrap + } + } + Image { + id: certScreenshot + Layout.alignment: Qt.AlignHCenter + height: 182 + opacity: certificateInstall.waitingForCert ? 0.3 : 1.0 + source: "/qml/icons/img-macos-cert-screenshot.png" + width: 140 + } + ColumnLayout { + Layout.fillWidth: true + spacing: 16 + + Button { + id: certInstallButton + Layout.fillWidth: true + colorScheme: wizard.colorScheme + enabled: !certificateInstall.waitingForCert + loading: certificateInstall.waitingForCert + text: "Install the certificate" + + onClicked: { + certificateInstall.waitingForCert = true; + Backend.installTLSCertificate(); + } + } + Button { + Layout.fillWidth: true + colorScheme: wizard.colorScheme + enabled: !certificateInstall.waitingForCert + secondary: true + text: qsTr("Cancel") + + onClicked: { + wizard.closeWizard(); + } + } } } } - // stack index 1 ColumnLayout { id: profileInstall diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml index 14e10f94..e5dd988e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml @@ -70,7 +70,7 @@ Rectangle { Layout.fillWidth: true colorScheme: wizard.colorScheme horizontalAlignment: Text.AlignLeft - text: (SetupWizard.Client.MicrosoftOutlook === wizard.client) ? qsTr("Are you unsure about your Outlook version or do you need assistance in configuring Outlook?") : qsTr("Do you need assistant is configuring %1?".arg(wizard.clientName())) + text: (SetupWizard.Client.MicrosoftOutlook === wizard.client) ? qsTr("Are you unsure about your Outlook version or do you need assistance in configuring Outlook?") : qsTr("Do you need assistance in configuring %1?".arg(wizard.clientName())) type: Label.LabelType.Body verticalAlignment: Text.AlignVCenter wrapMode: Text.WordWrap diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 303498f8..7e04eedc 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -21,21 +21,33 @@ import ".." Item { id: root - property var wizard property int iconHeight - property int iconWidth property string iconSource + property int iconWidth + property var wizard - function showClientConfigCommon() { - const clientName = wizard.clientName(); - titleLabel.text = qsTr("Configure %1").arg(clientName); - descriptionLabel.text = qsTr("We will now guide you through the process of setting up your Proton account in %1.").arg(clientName); - icon.source = wizard.clientIconSource(); - icon.sourceSize.height = 264; - icon.sourceSize.width = 263; - Layout.preferredHeight = 72; - Layout.preferredWidth = 72; + function showAppleMailAutoconfig() { + titleLabel.text = ""; + descriptionLabel.text = qsTr("Apple Mail configuration is mostly automated, but in order to work, Bridge needs to install a certificate in your keychain."); + linkLabel1.clear(); + linkLabel2.clear(); + iconSource = wizard.clientIconSource(); + iconHeight = 80; + iconWidth = 80; } + + function showAppleMailAutoconfigCertificateInstall() { + console.error("showAppleMailAutoconfigCertificateInstall"); + showAppleMailAutoconfig(); + linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why is this certificate needed?")); + } + + function showAppleMailAutoconfigProfileInstall() { + console.error("showAppleMailAutoconfigProfileInstall"); + showAppleMailAutoconfig(); + linkLabel1.setLink("", qsTr("")); // We are not clearing to keep occupying the vertical space. + } + function showClientSelector() { titleLabel.text = ""; descriptionLabel.text = qsTr("Bridge is now connected to Proton, and has already started downloading your messages. Let’s now connect your email client to Bridge."); @@ -46,22 +58,22 @@ Item { iconWidth = 264; } function showLogin() { - showOnboarding() - } + showOnboarding(); + } function showLogin2FA() { - showOnboarding() + showOnboarding(); } function showLoginMailboxPassword() { - showOnboarding() + showOnboarding(); } function showOnboarding() { titleLabel.text = (Backend.users.count === 0) ? qsTr("Welcome to\nProton Mail Bridge") : qsTr("Add a Proton Mail account"); descriptionLabel.text = qsTr("Bridge is the gateway between your Proton account and your email client. It runs in the background and encrypts and decrypts your messages seamlessly. "); linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why do I need Bridge?")); linkLabel2.clear(); - iconSource = "/qml/icons/img-welcome.svg" - iconHeight= 148; - iconWidth = 265; + root.iconSource = "/qml/icons/img-welcome.svg"; + root.iconHeight = 148; + root.iconWidth = 265; } Connections { @@ -83,11 +95,11 @@ Item { Image { id: icon Layout.alignment: Qt.AlignHCenter | Qt.AlignTop - Layout.preferredHeight: iconHeight - Layout.preferredWidth: iconWidth - source: iconSource - sourceSize.height: iconHeight - sourceSize.width: iconWidth + Layout.preferredHeight: root.iconHeight + Layout.preferredWidth: root.iconWidth + source: root.iconSource + sourceSize.height: root.iconHeight + sourceSize.width: root.iconWidth } Label { id: titleLabel @@ -97,8 +109,8 @@ Item { horizontalAlignment: Text.AlignHCenter text: "" type: Label.LabelType.Heading - wrapMode: Text.WordWrap visible: text.length !== 0 + wrapMode: Text.WordWrap } Label { id: descriptionLabel diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 904a66e4..76bd4fc3 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -40,7 +40,6 @@ Item { property string address property int client - property string clientVersion property ColorScheme colorScheme property var user @@ -82,9 +81,9 @@ Item { } function showAppleMailAutoConfig() { rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; - leftContent.showClientSelector(); + leftContent.showAppleMailAutoconfig(); rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigAppleMail; - clientConfigAppleMail.showAutoConfig(); + clientConfigAppleMail.showAutoconfig(); } function showClientConfig(user, address) { root.user = user; @@ -93,12 +92,12 @@ Item { leftContent.showClientSelector(); rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigSelector; } - function showClientParams() { - rootStackLayout.currentIndex = SetupWizard.RootStack.ClientConfigParameters; - } function showClientConfigEnd() { rootStackLayout.currentIndex = SetupWizard.RootStack.ClientConfigEnd; } + function showClientParams() { + rootStackLayout.currentIndex = SetupWizard.RootStack.ClientConfigParameters; + } function showLogin(username = "") { rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; root.address = ""; @@ -109,8 +108,8 @@ Item { } function showOnboarding() { rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; - root.address = "" - root.user = null + root.address = ""; + root.user = null; leftContent.showOnboarding(); rightContent.currentIndex = SetupWizard.ContentStack.Onboarding; } @@ -154,6 +153,16 @@ Item { clip: true width: 364 wizard: root + Connections { + function onAppleMailAutoconfigCertificateInstallPageShown() { + leftContent.showAppleMailAutoconfigCertificateInstall(); + } + function onAppleMailAutoconfigProfileInstallPageShow() { + leftContent.showAppleMailAutoconfigProfileInstall(); + } + + target: clientConfigAppleMail + } } Image { id: mailLogoWithWordmark @@ -161,10 +170,10 @@ Item { anchors.bottomMargin: 40 anchors.horizontalCenter: parent.horizontalCenter height: 36 - width: 134 + source: root.colorScheme.mail_logo_with_wordmark sourceSize.height: 36 sourceSize.width: 134 - source: root.colorScheme.mail_logo_with_wordmark + width: 134 } } Rectangle { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-macos-cert-screenshot.png b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-macos-cert-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..e0ac953d1ac311b555020d1fce2c7f9c808b80ab GIT binary patch literal 9410 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 Date: Wed, 23 Aug 2023 12:28:48 +0200 Subject: [PATCH 44/93] feat(GODT-2772): Apple Mail profile install page. --- .../bridge-gui/bridge-gui/Resources.qrc | 1 + .../qml/SetupWizard/ClientConfigAppleMail.qml | 120 +++++++++++++----- .../bridge-gui/qml/SetupWizard/LeftPane.qml | 22 ++-- .../qml/SetupWizard/SetupWizard.qml | 3 +- .../icons/img-macos-profile-screenshot.png | Bin 0 -> 12807 bytes 5 files changed, 101 insertions(+), 45 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/icons/img-macos-profile-screenshot.png diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index 90488c98..aa184628 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -53,6 +53,7 @@ qml/icons/img-client-config-selector.svg qml/icons/img-client-config-success.svg qml/icons/img-macos-cert-screenshot.png + qml/icons/img-macos-profile-screenshot.png qml/icons/img-mail-clients.svg qml/icons/img-mail-logo-wordmark-dark.svg qml/icons/img-mail-logo-wordmark.svg diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml index 61c30eb3..e39cfeb9 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml @@ -103,7 +103,6 @@ Item { } } Image { - id: certScreenshot Layout.alignment: Qt.AlignHCenter height: 182 opacity: certificateInstall.waitingForCert ? 0.3 : 1.0 @@ -115,7 +114,6 @@ Item { spacing: 16 Button { - id: certInstallButton Layout.fillWidth: true colorScheme: wizard.colorScheme enabled: !certificateInstall.waitingForCert @@ -142,42 +140,104 @@ Item { } } // stack index 1 - ColumnLayout { + Item { id: profileInstall Layout.fillHeight: true Layout.fillWidth: true - Label { - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - colorScheme: wizard.colorScheme - horizontalAlignment: Text.AlignHCenter - text: "Profile install placeholder" - type: Label.LabelType.Heading - wrapMode: Text.WordWrap - } - Button { - Layout.fillWidth: true - Layout.topMargin: 48 - colorScheme: wizard.colorScheme - text: "Install Profile Placeholder" + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: 24 - onClicked: { - wizard.user.configureAppleMail(wizard.address); - wizard.closeWizard(); + ColumnLayout { + Layout.fillWidth: true + spacing: 16 + + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: "Install the profile" + type: Label.LabelType.Title + wrapMode: Text.WordWrap + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + 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.") + type: Label.LabelType.Body + wrapMode: Text.WordWrap + } } - } - Button { - Layout.fillWidth: true - Layout.topMargin: 32 - colorScheme: wizard.colorScheme - secondary: true - text: qsTr("Cancel") + Image { + Layout.alignment: Qt.AlignHCenter + height: 102 + opacity: certificateInstall.waitingForCert ? 0.3 : 1.0 + source: "/qml/icons/img-macos-profile-screenshot.png" + width: 364 + } + ColumnLayout { + Layout.fillWidth: true + spacing: 16 - onClicked: { - wizard.closeWizard(); + Button { + Layout.fillWidth: true + colorScheme: wizard.colorScheme + text: "Install the profile" + + onClicked: { + wizard.user.configureAppleMail(wizard.address); + wizard.showClientConfigEnd(); + } + } + Button { + Layout.fillWidth: true + colorScheme: wizard.colorScheme + secondary: true + text: qsTr("Cancel") + + onClicked: { + wizard.closeWizard(); + } + } } } } } -} \ No newline at end of file +} + +// Label { +// Layout.alignment: Qt.AlignHCenter +// Layout.fillWidth: true +// colorScheme: wizard.colorScheme +// horizontalAlignment: Text.AlignHCenter +// text: "Profile install placeholder" +// type: Label.LabelType.Heading +// wrapMode: Text.WordWrap +// } +// Button { +// Layout.fillWidth: true +// Layout.topMargin: 48 +// colorScheme: wizard.colorScheme +// text: "Install Profile Placeholder" +// onClicked: { +// wizard.user.configureAppleMail(wizard.address); +// wizard.closeWizard(); +// } +// } +// Button { +// Layout.fillWidth: true +// Layout.topMargin: 32 +// colorScheme: wizard.colorScheme +// secondary: true +// text: qsTr("Cancel") +// onClicked: { +// wizard.closeWizard(); +// } +// } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 7e04eedc..64d11203 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -26,28 +26,24 @@ Item { property int iconWidth property var wizard - function showAppleMailAutoconfig() { - titleLabel.text = ""; + function showAppleMailAutoconfigCertificateInstall() { + showAppleMailAutoconfigCommon(); descriptionLabel.text = qsTr("Apple Mail configuration is mostly automated, but in order to work, Bridge needs to install a certificate in your keychain."); + linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why is this certificate needed?")); + } + function showAppleMailAutoconfigCommon() { + titleLabel.text = ""; linkLabel1.clear(); linkLabel2.clear(); iconSource = wizard.clientIconSource(); iconHeight = 80; iconWidth = 80; } - - function showAppleMailAutoconfigCertificateInstall() { - console.error("showAppleMailAutoconfigCertificateInstall"); - showAppleMailAutoconfig(); - linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why is this certificate needed?")); - } - function showAppleMailAutoconfigProfileInstall() { - console.error("showAppleMailAutoconfigProfileInstall"); - showAppleMailAutoconfig(); - linkLabel1.setLink("", qsTr("")); // We are not clearing to keep occupying the vertical space. + showAppleMailAutoconfigCommon(); + descriptionLabel.text = qsTr("The final step before you can start using Apple Mail is to install the Bridge server profile in the system preferences.\n\nAdding a server profile is necessary to ensure that your Mac can receive and send Proton Mails."); + linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why is there a yellow warning sign?")); } - function showClientSelector() { titleLabel.text = ""; descriptionLabel.text = qsTr("Bridge is now connected to Proton, and has already started downloading your messages. Let’s now connect your email client to Bridge."); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 76bd4fc3..2bc5e9d4 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -81,9 +81,8 @@ Item { } function showAppleMailAutoConfig() { rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; - leftContent.showAppleMailAutoconfig(); rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigAppleMail; - clientConfigAppleMail.showAutoconfig(); + clientConfigAppleMail.showAutoconfig(); // This will trigger signals that will display the appropriate left content. } function showClientConfig(user, address) { root.user = user; diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-macos-profile-screenshot.png b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-macos-profile-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..68a87341491e2a89a2c28d15a148b0db294f4474 GIT binary patch literal 12807 zcmZv@WmFu^^EONZ2~L8$1P$&G+}#}(2^(14eQ|fU#R=|iK?1?uc5!!?;K3j6-~YVd z-acohX8LrWnW>qny6URx2vubnbQB^KI5;?TIax_{IJkE!~|$;h5iB~>YI z*~q0l-b13fONBsq=Vw`eLZRxBl9b7H8c5#%8~v3lG%T#Ft1Ir~!=nlvz0>6F>9UG7 zi?GYasm9F>DvsW~{wJ5<(~XS{Hi3h)v*MgHMvBwZX?Ki{=iTz7BUWB6CN!ZxRETda zY|&p`*uvD~moO$dam3f(4-rR4ME`e7nf{um3GNWA^53?`4*wh5iu?~36AkOXiD816 zadB!K31eryfvHC-moToobhHm5lUfrBa&mIge-MLO_p;G8Q5 zT$Ijp*ug<*b8+tow>ZXI15+f(sxlF|ni?Aa6f~(ztNz@}R>O4C*(prAys)q!3lmdO z`7}E>+k-3$>)+U*Ku5<=v-M%Vxx1^t{~|p*JL}T=%)zW0`*Ei2+2^_OCorI?OcN%E_!m$qMD^lu^w>c1H797)GydE|b)dx;Ij=nxyOQ;VGAjn`4<-y~R9f$Gk` zBNBc_i!t)l@KI51v{uP za?zyS>oVAbGYGWXc=gNg)pzN)Ij`+nWBSX*>k}0^8k(xDE%Wm~nb*vWjEsYzPa=gv z?e4If`R^)8d||o150LBIWqVjUr+EsVb~!9PdWY5Em#B)+4VSB%8?;*@%tflv?^U1w z)_Hd%WoN_q{W&9(7&ep*iZjWVPWXn zq}4>^Ga3Z1UBCKU&jTA^rbYEBI&(Up~#t7|ds!YFNL+NNcb0dtKO^+xrk z$jpGo@Q2y`ChM94EZ1#8sWxV2=A|zQ=pz5RKb4lZMe_469e!%Rn|g3PzLrQpfBVaR zy03x5$bA1{0-9~;ne{p(g*0oh&iEOS5fQP0!9M@~0-JsZyRCULR_Qf_#*zw1n-rKd zI&6w(Op2o3lKWm_^ZUIB#>B)N7F9GvDSyRStlKslr$_tjCXVUG6kO|^Dw+8Fo9lW^ zrSzjV$V$J6a$)lh13H8#Q)cr_TXzo5zY;0PI8wJwDF`ss(vuD{?8>VPB`z%lDtLaM z8HxX3WqthBA=|TU?(p;!w)|!zEcky-c?RS>-Z*EJ+IL(?Gawij*{`;xtf_snfL-n` z{m5moEU;{}x##c+iq~$ECr-T{NvY86SFL8O-L>-K!W*amZFsjLLCjchkE2py(n`L5JmVH8y#DiahmSPl3*bjhM|Pt z^}2d)d2=+|2);67pxzk%ZFdS_rvKTK&amcnF64Qen=?CiWAFQ@&zxY;>LfMHt76?F zc>D5reY~Fo<9B2Ec6o;BdAn%dMBQYw$WcUxF}3QxE*0l4}yz`OeO^5asi1z3{M=y_M zYE1|vBm@+oL$+Q{bQ55w0Npg;BS4?kYW61(2;XFc=HWnblPTF;+MUI0^XWs)wUsZ^k>IAHfghJju^rasP8ZEJf9*S6tka5RhhsUW40WLm+;rULAk^~Q-3V3c9U$t=q8znr-ce zG9!5r89v(sG1#&D^C)39%gsvbnbp;dGiz(rNbRX_&bI_0sRk&O_2ZvERqa6X3Yt=v z_~84p!uM=&{_Bu-^+d$=NR$84Liqhhm^SF|Pm)+ahpgCAZSEl=&}`*DD)OBZm+P6) z?``!V(l~l?F7DH8HnZc)r4M;BU)41rtzGyd$L?p*W_xxw0OOtJ;aKjV*c6?(-6yj`a3+KNM z(pHm#{Wby+lY<=Vnk$L5Y`cE+qm)-v6lhiG&`ZyeTVAi}W_t|vC2|0pTzr9$F^LqF-Sd@zpt(&-UQE&{4Ecc4CwP8F)kOqM%hvnv%|o+ z!ju^K<%D}P8*uh1Z)Xk)jo>&=hn4K<45o_*<9EwDn4YrbZnpIp*akytqb%J210`&L zDX7a=LDlNTLaKf4H@q{1>L7W%iI!~NtMI&wKvrZ&M70LWvf}r&$-CrO~%WS;f2NVh&cVc3EO97VIUR%?7E<@v9`dMc-tZCqrWLyM-e(M* zt^ZJt)O(qQ|9^SvLP9`-wAx+ZfLN;1KMR}g?(WV@%R~0U3G)5+UmLTu=$UXmQ2sx( zCT6eF0;?q@At8zU5c_`;;s2A>)@?ySz1;9)#@`mm^S(4i$6&@_fP(`*Jez(##*6re z!`PoyC!CUkt))p>Pjz8)tf`PZyYJWiknm!CK&PWL13U9 zdW8@NjvR~dY{U6b{>*rssM$^-H|r{wG3xLiz*KEKIFC^EvQEZwjHAq{8D3-r_S^;` z_r{n!&4{e)3{v|jZS}&Fq2Ef;<3*|WCL+x8j45ro{coxZ0WnRR3yXAbP!8>(uA<%) zg*8Y{MiiQm9DtyJlxd2%N;Z&|-?UWfP-Hkv z4knIQpu$y^zN<*+^!w)m1`=1PzXgSb8IrUO7SsoW6~t`abBs5+I1IxNK!KRGn8J- z-XN*is>8_0cw?UBZ=Sg^>8<|E_YWveBxcLo*_F#8CSO8xcUNEdrOTO!Z=c3hRN5$d zk@K=OC+q4*EBjECBGr8T>FMFHcL4N0@(o9h2P0b7*Jmpm^^AbZ?-o-bWh>_Cu^nVr zZBD=6=5WA+tVdBzim%j%9!XLmx7zt?Mcnf<3AD!+BWbGnbn&;%OYzdoj-mMhP4fR$vC7BmbDhPCJr<=d(Gd6~LWnN~_xRAT2C zAQ5p`>pPJG267q6bb_>=;qFa9H%Mykx1l5mH?mQ5qRziIz4C@1%X?0{nc8L>Hz623 z@m51}Ja}9(JG>-Zzv{83)S2euhz{tI8y8VE?WBo}!SesbcaN~)*wVDsoN z?tq#ll1^Nb=Ul0!<|u6QkxkL_bvXE%Dk2L27dDnT=nHFWMn^|SP!1GY)=r1bo@6Ai zx%T3MVdHG@BHS$@5fREW`r#OFa0-BB_i#fM?c3d#L&WT1kN0{dsk(BtLzC>Nc_tA> zOgGoQTi<^BMbz%}=X*&oVA%^Pf#KD|y;%pR(4A4z@PGxy9-ATf{{vpD?d}@Z){MJR z^G04uy!f=efB&+bF1JiR5VO2&bO&**ZzNEeg<0dTk`{VVN#~~nT)BcfVANSo(&{{* zDFr?z^uT>k;0+n^{BT$@cKtaDE&|1c_u5vVET#LZE52c;ce?P?UkL_Q){+95IIGEn za;*vrV`EWlCnY8Ho9kH^e{2$~b@LQ{IIO8o&v;J-V20a^npFRKc zHcc#1>&r_^*I)()hJ#RIyPqv{b?d8s?dpWc~Vh$(=x)Bm`ZE4iDHfMoT5`&T3U@xyYjYLK?Zf`JF(oimUeb?%j<7o z_~AfCq@3AFcl|9;YJ79pO1Gq>q>XN;0HwKjm3UfllMNpsuiMJh?-?x;g=q-2> zX!h1O$x(-B!ns0jE#yC9V#!K|q7>(=NQVXzoF1SJEP=x?I!Mj7UY!WW+q{qY&Vbg{I82z}QE7D%ng4FYFC?6c@5SoxtZyA7fhUAy3GNVps~% zX9X0|oBsqrk`<%Rei4ZpqAd2dMSrwL@Z_Q|YHk)5B)vB66FNOPDa58zKCt~&v(W7G z=+@@6iy}qU>iUz<@2oPaH)j&g(!Z7_f{iad_)Ck)yE3*`PN1AZHtYwKQ%)1BnR=7$ z*p0&pVkw)y+a$N(&C(nX-rNQW_aJ57B@K?av_}6;ukucAXsm6`hyRz>x)mhaIfY_k zP~|=B^@)=B)U5Vyn&!THwgS zs}H0C#6Wza*xDMY9g8&ffQ=$k)Xd>tY0t#0w8jPTo$Gi!Jj;H0@g!4b{CjA|WdwbV z><~Mhhi!$0A~Z$2hCW2rTHlEAJNCYe_UFBOHQMIwwAxngo=4|J1vx7oW}6-SyJ;I5 z17?TCjjUdHc|7sf@UvZl40{{J`(B1*Z_R1|H7KXKwuVS_fhGVKJ<1b}2${yh!$NVl zipzC95NKqM1;*qV^J4lC<|7+@5;jy^=h#7_Xl6y2CLa2aK!{@E7up}H{R?yq5tRkL zpi>}`ot|aHbG(65CDb%3X;|vzqip@oY2wpP#&9^Zteo%Oh0X5BwURpTB;9TTn{^zr zDprLc&saTV(8(y?{fY>7rxo8^2}eE5Nq^O9$~QVUXn>%@aUd;(EvhHlE0bm~cvcc( zNqd6OM&&8yi&CYpzX^(!dVk0sUp^x=+Zl%S+s7l`3`SQiNTC$!@mG+r$~cyF zsGtyJH^*objqrU8-yB*D30W@G&PdP6-I^*}-K41U54#s^zETz(7@cwiuiM<$COG{Q z7S__%*1H?t2PmQH?r9hy>U%5xQEzY7tMg`3z&MdiGQ~I3%e;(8X7gC?yBrD@4C|o1 z;^Q2KTro6RHhX}$5}7}F3qs;7X%tY&x`sItsoKViLE{@@k!D1{Zl^K7ThAGwWz?p) z*W}l8QK&MY0jc8Ot@h@zQbl|GFyojEaYd$Fmky=JLuaT@$50)Os=a}yONS<{|E-Tr z;N`P#eLTLa-7Y>J^m4+OYj-(n9gG1WNcleDOopnxrOlU7z}o0KO?%3_u# zxpQ)CMdEUGMOwJ(1F{W0*!7%K5v%|$>R1VVABTzsD=FM|HaY#wFFBdD^@K&!ys@Mg z6n!t(8jQx5i%h1O7}GlmpL-7FqE-d9*NF9mp^l@LwX-a7n@-6o8#YO9&E^No>n}V) z7k${8`uYkw@ZlX_<=B%T3Av6H`df!3xy8)-%~Jz0#8rpgb$6GetT2gBLdkW{>E=0k zVdTDPz7McmC~xOQ4DujI6RphE*n}3goamfh%V~rY&3BBry?%s`Aa}SgAWjHBg)&N{ z-4q8|8f5B!=eiwVn0rW)X#G*759*o38Xmktr&FPoGx(8MFIdb9GHgm|yQxsYsGqab zbm0yjhn0C#f9puh>;fJxF_1Q_@JYtNKu%kwh;ywZ5cyf$Y4Ix^wQLm}LK#BCZFdZ&XEAFRB|^0Vyna#xguS z&?H;MWMHlNDOKKD_Y;`6Hh32V874j~!Sa~wAEhloOOu4tcm*Bn@r)PI8DYgQQtvjM z(j-3?c5Sm_*`>%)g4lox(D-qqWmIN4J$a-I<9gkHJUb8d4I$YvmA014;aY%XCQ`Qh z^tz>H>x8?3r&Ve*Y_p6f634|%kQLPrxyE8{GHqcqR57EV@x)2DaVRur)O7sMFR=rl z_nXs)xpzGpDlNqc-4c-UyT~SSJSck~>#ygNzx)l$;1an?pFD`)Fd2-UW^P%@7Z?n| za&vh~fE4AdnSnJObEolltiL}J`Rog<=N(tKE9bVfw(9AxzcGj5w^0`ZsQz&({&Ip4e z%E&+jsw#G+(J$xWgzx^~WfI!5Ro{Iv6ghR+8i9Yj5Z$8%X6EY2$AdQnuY?!lE8fMr zq}TF24?~-u?M9HH<>O|Hdrr#wAyxtdyZ}oMc?m4a5+GUc^4KG%UPbTNvZnFc>kqi_ z^KAFE=~ONVGr*ccEH!`4`%39q_|om#L zv55L3$Vx~>#OHFb(d&*}Q>i)7ZN(d#o5^B|`(lx^$)Wn1QuMi{D=5={KrjYCT|V1Sa8R~YhB=J4wW!LN!~i<_fT9mMbYCk3cCjX1N7{(#V8J z(RWBGNVkJt{_;q{agC~ZrkYs3Ry{#nh|?=AwLaSt29vo-ng{}-Dz8uGjpsM>hto3T z1?^{xQeo?Sl5e!9y{oO;dG0eIsrq=~y5Jpgd0BPQq|lSpsclMI3B8(Z1>J%&;PsaB zmZdKOM^0W&NlI!6b^8OL;kawwgkw9`szmI?LG?Dc#ew} zK9E!`gq<>T{!d2lgg?PU5&iQx2&S6eeg1{KMs6%)#pdj3l?YF3bpzJi?baA5&dsQk zrtgFWx)kpPb`(eke!`g%jAxgYIT#BYJe+;Q!-(YG{&+L<(`!P0Qz6T+S-F~QA&Hu@ z^%r$;isKZc--<%Zx+5a<_qxa*miQHrJWi*dod%V8I%-X^rdBb~2<#Yf7W!$tipBvj za6irRw>+WA$^*@*DB1+J;8&Y_`6vLH`B6$@U(pMh7^?r*?T*V(c~k*%jtS%u8gh{* zo@t>^@gm7VWODw9Ume?tk%Zespw;K6ZSSjm|pZn+RyEPQ3YR;$Gm8Mge zBM%S<@790C3}AFgMWC9OJ-zY$_Eh#KMW&BZku3ee9oJ(yEVnw- z;bK|n6(p9HMIGD|!{)e#_(Goy)KdAkYE3d^ljP3c>3RGG?_%i;SgP2I%xcaePJdzW zOM#&!59_?obV`=}D^wN^PP^@DjniiOE}h9^pV#%A*)?r?3sLdTmRHEvEJt}DEus>7 zJ^l_W`2K>*UnMwv-gU>qtc?+n20?+xA)>N!k^5sdluB4cara@f?snA39fJ`yJZ1UY zlG#MOglbR{?=&GHZ3>$jXF@}g+EJ@)oPF2?V~k9n>1Veiq9PVMqg?K`!l|9?o*`?m z(x;cn>4UZXS$!_q0{fQ)c@ZZ>n)Y^!ow%-ozTK#$o?C`;3(W*@{cl_E*EL42`g0nF zMMq+Ep(||pTi4_DP`_73r>|ri;EIY0STBL|L5W{V4zD2Wgd((n&4Zj?X~e^JcKIjM z4S=Su+`Q_2y%aUKK=kJ{)DJXWecL%_v1E#U$A@)NA0K7Pnv2C+mXhchyUH`na9~)t zV*ZX=RBo&0IryWq{|1ia#=SmW;?AG$a)^;~OubshidHBvJi4|fWDhsoIrPzJ&1hr<%{40_EWQlm*ptBn##v_eue1TCy6RS@|1 zh=Ss4tl;d-_=&8KXikZO<(EEcWI1YrbADl-@-?Bvjj2BikhVToDSXX$t2*Rq2eJRDf*+-W`h?J!E|7g36*+wH%9HhZ4v$OxF4BNRhvQI13n<7rcPu7U(I z)D*TRg~EO-mo?Ufgo8LoVHfIhUzHG7xxPF|?3Gqjpv%e_j?)R>J3fSUz1E$*@D+CS zSArj+nx;(`kC^{Yt+{4QKaoJ7q+ zN%aI2-zjKg8h%Ttucmqj-cpnIOgA0RL+un1ZWR+YrkvX$A*Zvv@=}k#OT^`39BSBB zG}9pN<6BrSvFasivwTzN7_97LF&Ip znh(C1{ba^?Zew2(ceWxo|Gg{VWgVt69rv>TsjRa)^yNszCrG=>Kx%w6x!>y^f23Sl z^)&^BWc;i)&5`$juA7DYQEZJ-6r1Y$O8^VZDO^UA3cDjRQCLCtTVsBjeb@M;TtpUd0NBkMwiWOCWz`8Uo?vU`f(W)w! zK~i^UL7cFBO60fiD_8HI58$Vl>f5Wm>l)^{3{b6 zi;}*i<{X6jqd1JysAW2JzfA_bHo3$?Ag>4#Gsr zGM~BkfjkK%A+`g2gN;&ALs8w`@2W_hZ8%UYTwJ1A21e-te&!B>AH5?zxJG*O%AYG3 zY2z-&MqSOnJyOKX%(MP%tL5q`v|1OAG65#@%{Pj|oWtzMIT!&p zT`23tpz&xviVFJZl(ytO@J(|kNC_gWH*o=@`5CW~5$qnvQ`Cb-)I zBVD~ZfeT2GKDKlxZwJJ%)>v5@N&)%O0-<6m!T0&MgAsrljmd`Yg>Kz>r|8oSPEQ>c zT@Gnqy{w5AWkI@u>D1o-39d^@8?v9nK)XMal*KzJ!Ad`9F}~Tdht@SrkVazuUX>Ig zmGoKd8Nsv4r)PL58)zp(Ry~aa+Ohe{X~hWn5O5H@q&x-~FDx$lo2*kxFA&h*$J?o> z#JQ2QrzT0T!~dqN`y=;lN`-4dAvl2Qi+yyAG1IZJ2pZmpz|b8vb)snVn0E|x#IB#t zanOidpvNaCGtZ)C@UgY~d~CNUZ#lzQ8;P~Oz%p6I5R2V~(D$j9`_357)T3oT7_c4E zOkkspHvU(by*YC;i<2UTZ%nYgeaT`V)d{6^8ugaTK@Xg1Odh^}I6F6~@q{tL(5BOF zqmvUQN~oani`hi8DKWmVe$IW?Nm6Z0x1F*Q9mFQdEDfBxzuQDYtM|s6UV8xJcX@=> z-1b(=%$D{pXvgZAw7>PR{J;^%avZ|2coJ-nKMjFMf~a_D$wZYg>EgE6sTYTx7in9O zGIcZ)NhY@_C&#y|m(6VIceT!X_j&1FHcal_`CQ3zWw2Zc4o8o-T&RJg>1jKXCes=z1`ld$~V!tQ3^VaohZ<{pe(tWV6vduDHd;e8RRU`9fQz%mg&0 zhCw8$ki9s1OzNdURCKrTrsflorlWByaj-r4KPS3o)|P2~MA~K@hSeZn2W9Z5cv_vp zMF2|Hrz^miyai9*6=d+`EqWoC&!-N|b7a@Wm&Ju?>{irlOAQDHr!DHgZ=(uju&}i) zDlRV0>UjDp6E8x69Mq+7*?3r)_0bs6r0BiZH_k&ZnOH2ya^js>s}v!@qWHs`xj4AT zIrcJ|QxKPIzq`@lex=??oCrFS$_&@Avg1x4!3G?)j^721D4K!(%v0<~G5=4qJ374h zh@Y{2-x5#O3jc-@s9Z*wTMm$!|Mma_QG&JJthSo7a|3PAC*gmh*zC0SLXKOK85@&1 zLKVO7Dbiwyd7+!VVK<^62O?l>kMsV-;ldTS^_+z`+%UX;i=C3n4w~`zKeS=B{q$X# zNzzv@f~tf;!j z%;?Us8lrpabLwonXnK4@BZG~ zjC>|Ejrq`f8px%b&0mC!hsXEBl>GodoEYJg970g|rql<*f?jwgx6gvINZ2O+m0~Lk zpSlX_MboInTNGt~j1~`aBxFGSgQvr~n>l7Jmgx(itz0)kB>l(@;HBc_LfdR+&e%+@t3#Q_a z9ek8qx%{R3J( z#o;)5IW9c}#5H5@!Mtq>r_`1dE!CxF%liP2$I~`_rO*NzO+{viG|rNR4zKPu&_54#x^`nt&?uI;0}(qckB#F=rT}1oqJ+J*5a2@dY@uy3tO^f zgF!$RWF`txce{&aV?#t5M2Wlo4vFEtl~gBnjC+Vq)!owh&9*#Hu80(161)wYTvSW? z4qelR!IgFKnI?e|>TdHYxoT*k>UfGUVf78+F5z9Z(BU(xYNhfqm&HBV!z8WwJMu za;|!_Y(Wx8qF4?N8PjORyEZ&mG`hSgbG2353Iy~^jX&fb5*ukYM=-*z}wq zoKybwG@jniWw-?UDD}uuhWN+S+K$^32ZSb&XPE&>RR|W5^z9n>UQK7h7Z;_^tfec{ zHskyK@1^Jb-@HL6ws18|+DTytu;D1Jl!kt3Xo(~_0(maFx4Gz3u-;YiENk~ZTM>c2 z)CSw#CQ^I&W=J&6)>>DOe}Uv!_$N*-MXWk%o3mIJW?fE%@tGJ~&n9MoKj*9!9v#Anm z=-SMuTQgD+D=K7njegBo92gp^eK;qLF3W#3u~#`?jfo-!;uH6fje3*oA_}O+9(<#U z;L(5LqCi%$>Qz+V`}?yMkkrv-30%6gA=}m?DdYf1?FrdT$lRyHHm??UD8asZw9x#0 z_U`n;MLEVITF!^iNy|hl7LY|MxqU+lnr@*$k==~|Z6PiC0qyN>7rBMEHW$oFs_?-!YX2q#*_>979Qt1FJaW zF`&TriIM}kO09i1IakfYrs;*CXv@~P4m=I4I`l#u?bZb*8{`)j^B{I+^Rn#G2yzFz z>WyFO301@tKM3EcrK&WihNs7-+uKNd+qc2ueu|VYr!Ddo#o((dkMVMO*-=J(TYLa= z&CF4SbjG|@uaS_Lq5owqXLyMlnf)h3Ft>2a+RGevveK)b_O@;VPEJZ$vRd3Y@c#k% CXY57* literal 0 HcmV?d00001 From 75ed3ca660e0993415eece7e4162974339ca587c Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Wed, 23 Aug 2023 13:10:51 +0200 Subject: [PATCH 45/93] feat(GODT-2772): QML import cleanup. --- .../bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml | 2 -- .../bridge-gui/qml/SetupWizard/ClientConfigEnd.qml | 2 -- .../bridge-gui/qml/SetupWizard/ClientConfigParameters.qml | 2 -- .../bridge-gui/qml/SetupWizard/ClientConfigSelector.qml | 2 -- .../bridge-gui/qml/SetupWizard/ClientListItem.qml | 4 +--- .../bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml | 8 +++----- .../bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml | 3 --- .../bridge-gui/bridge-gui/qml/SetupWizard/Login.qml | 7 +++++-- .../bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml | 3 +-- .../bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml | 6 ++---- .../bridge-gui/qml/SetupWizard/StepDescriptionBox.qml | 4 +--- 11 files changed, 13 insertions(+), 30 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml index e39cfeb9..f820067e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml @@ -14,8 +14,6 @@ import QtQml import QtQuick import QtQuick.Layouts import QtQuick.Controls -import QtQuick.Controls.impl -import Proton Item { id: root diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigEnd.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigEnd.qml index 02b80f6c..3893f7e4 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigEnd.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigEnd.qml @@ -14,8 +14,6 @@ import QtQml import QtQuick import QtQuick.Layouts import QtQuick.Controls -import QtQuick.Controls.impl -import Proton Rectangle { id: root diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml index e5dd988e..366a55d3 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml @@ -14,8 +14,6 @@ import QtQml import QtQuick import QtQuick.Layouts import QtQuick.Controls -import QtQuick.Controls.impl -import Proton import ".." Rectangle { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml index 2de2c47a..51aaf5ea 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml @@ -14,8 +14,6 @@ import QtQml import QtQuick import QtQuick.Layouts import QtQuick.Controls -import QtQuick.Controls.impl -import Proton Item { id: root diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientListItem.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientListItem.qml index 5422cdfe..8a133605 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientListItem.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientListItem.qml @@ -14,8 +14,6 @@ import QtQml import QtQuick import QtQuick.Layouts import QtQuick.Controls -import QtQuick.Controls.impl -import Proton Rectangle { id: root @@ -33,7 +31,7 @@ Rectangle { return colorScheme.interaction_default_active; } if (mouseArea.containsMouse) { - return colorScheme.interaction_default_hover + return colorScheme.interaction_default_hover; } return colorScheme.background_norm; } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml index 47d4d357..e897b11a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml @@ -14,8 +14,6 @@ import QtQml import QtQuick import QtQuick.Layouts import QtQuick.Controls -import QtQuick.Controls.impl -import Proton Button { id: root @@ -28,10 +26,10 @@ Button { anchors.rightMargin: 32 colorScheme: wizard.colorScheme horizontalPadding: 0 - icon.source: "/qml/icons/ic-question-circle.svg" - icon.height: 24 - icon.width: 24 icon.color: wizard.colorScheme.text_weak + icon.height: 24 + icon.source: "/qml/icons/ic-question-circle.svg" + icon.width: 24 verticalPadding: 0 onClicked: { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 64d11203..a8bfe9a4 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -14,9 +14,6 @@ import QtQml import QtQuick import QtQuick.Layouts import QtQuick.Controls -import QtQuick.Controls.impl -import "." as Proton -import ".." Item { id: root diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml index 35fd9a1e..43146d25 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml @@ -14,8 +14,6 @@ import QtQml import QtQuick import QtQuick.Layouts import QtQuick.Controls -import QtQuick.Controls.impl -import Proton FocusScope { id: root @@ -41,6 +39,11 @@ FocusScope { loginLayout.reset(clearUsername); totpLayout.reset(); mailboxPasswordLayout.reset(); + if (username.length === 0) { + usernameTextField.forceActiveFocus(); + } else { + passwordTextField.forceActiveFocus(); + } } StackLayout { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml index bebe474c..22bf5008 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml @@ -14,11 +14,10 @@ import QtQml import QtQuick import QtQuick.Layouts import QtQuick.Controls -import QtQuick.Controls.impl -import "." as Proton Item { id: root + property var wizard ColumnLayout { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 2bc5e9d4..50ae4230 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -14,9 +14,6 @@ import QtQml import QtQuick import QtQuick.Layouts import QtQuick.Controls -import QtQuick.Controls.impl -import "." as Proton -import ".." Item { id: root @@ -102,8 +99,8 @@ Item { root.address = ""; leftContent.showLogin(); rightContent.currentIndex = SetupWizard.ContentStack.Login; - login.reset(true); login.username = username; + login.reset(false); } function showOnboarding() { rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; @@ -152,6 +149,7 @@ Item { clip: true width: 364 wizard: root + Connections { function onAppleMailAutoconfigCertificateInstallPageShown() { leftContent.showAppleMailAutoconfigCertificateInstall(); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml index ff4bf37f..74e8c02d 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml @@ -14,8 +14,6 @@ import QtQml import QtQuick import QtQuick.Layouts import QtQuick.Controls -import QtQuick.Controls.impl -import "." as Proton Item { id: root @@ -57,9 +55,9 @@ Item { Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.fillHeight: true Layout.fillWidth: true + color: root.colorScheme.text_weak colorScheme: root.colorScheme text: root.description - color: root.colorScheme.text_weak type: Label.LabelType.Body verticalAlignment: Text.AlignTop } From f617a44d288d8a0dc745f8eb9db75ed7f3bb7a97 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Wed, 23 Aug 2023 16:59:14 +0200 Subject: [PATCH 46/93] feat(GODT-2772): link for Apple Mail manual configuration. --- .../bridge-gui/qml/Proton/LinkLabel.qml | 13 ++++- .../qml/SetupWizard/ClientConfigAppleMail.qml | 49 +++++-------------- .../bridge-gui/qml/SetupWizard/LeftPane.qml | 1 + .../bridge-gui/qml/SetupWizard/Login.qml | 3 +- 4 files changed, 26 insertions(+), 40 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/LinkLabel.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/LinkLabel.qml index 4b9274e6..2b1feefd 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/LinkLabel.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/LinkLabel.qml @@ -15,20 +15,31 @@ import QtQuick.Controls Label { id: root + + property var callback: null + function clear() { + callback = null; text = ""; } + function setCallback(callback, linkText) { + root.callback = callback; + text = link("#", linkText); + } function setLink(linkURL, linkText) { + callback = null; text = link(linkURL, linkText); } type: Label.LabelType.Body onLinkActivated: function (link) { - // if the link is "#", the user is indicating he will provide its own link activation handler. if (link !== "#") { Qt.openUrlExternally(link); } + if (callback) { + callback(); + } } HoverHandler { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml index f820067e..bdef4a75 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml @@ -30,13 +30,19 @@ Item { function showAutoconfig() { certificateInstall.waitingForCert = false; if (Backend.isTLSCertificateInstalled()) { - stack.currentIndex = ClientConfigAppleMail.Screen.ProfileInstall; - appleMailAutoconfigProfileInstallPageShow(); + showCertificateInstall(); } else { - stack.currentIndex = ClientConfigAppleMail.Screen.CertificateInstall; - appleMailAutoconfigCertificateInstallPageShown(); + showProfileInstall(); } } + function showCertificateInstall() { + stack.currentIndex = ClientConfigAppleMail.Screen.ProfileInstall; + appleMailAutoconfigProfileInstallPageShow(); + } + function showProfileInstall() { + stack.currentIndex = ClientConfigAppleMail.Screen.CertificateInstall; + appleMailAutoconfigCertificateInstallPageShown(); + } StackLayout { id: stack @@ -59,18 +65,17 @@ Item { Connections { function onCertificateInstallCanceled() { - // Note: this will lead to an error message in the final version. + // Note: this will lead to a warning message in the final version. certificateInstall.waitingForCert = false; console.error("Certificate installation was canceled"); } function onCertificateInstallFailed() { - // Note: this will lead to an error page later. + // Note: this will lead to an error message in the final version. certificateInstall.waitingForCert = false; console.error("Certificate installation failed"); } function onCertificateInstallSuccess() { certificateInstall.waitingForCert = false; - console.error("Certificate installed successfully"); root.showAutoconfig(); } @@ -209,33 +214,3 @@ Item { } } } - -// Label { -// Layout.alignment: Qt.AlignHCenter -// Layout.fillWidth: true -// colorScheme: wizard.colorScheme -// horizontalAlignment: Text.AlignHCenter -// text: "Profile install placeholder" -// type: Label.LabelType.Heading -// wrapMode: Text.WordWrap -// } -// Button { -// Layout.fillWidth: true -// Layout.topMargin: 48 -// colorScheme: wizard.colorScheme -// text: "Install Profile Placeholder" -// onClicked: { -// wizard.user.configureAppleMail(wizard.address); -// wizard.closeWizard(); -// } -// } -// Button { -// Layout.fillWidth: true -// Layout.topMargin: 32 -// colorScheme: wizard.colorScheme -// secondary: true -// text: qsTr("Cancel") -// onClicked: { -// wizard.closeWizard(); -// } -// } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index a8bfe9a4..3bf09596 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -40,6 +40,7 @@ Item { showAppleMailAutoconfigCommon(); descriptionLabel.text = qsTr("The final step before you can start using Apple Mail is to install the Bridge server profile in the system preferences.\n\nAdding a server profile is necessary to ensure that your Mac can receive and send Proton Mails."); linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why is there a yellow warning sign?")); + linkLabel2.setCallback(wizard.showClientParams, qsTr("Configure Apple Mail manually")); } function showClientSelector() { titleLabel.text = ""; diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml index 43146d25..bc6d1f24 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml @@ -274,10 +274,9 @@ FocusScope { } } LinkLabel { - id: linkLabel Layout.alignment: Qt.AlignHCenter colorScheme: wizard.colorScheme - text: linkLabel.link("https://proton.me/mail/pricing", qsTr("Create or upgrade your account")) + text: link("https://proton.me/mail/pricing", qsTr("Create or upgrade your account")) } } } From b3a5270bdc220693dfd5c7f1c4837b9e12b12afa Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Wed, 23 Aug 2023 17:09:03 +0200 Subject: [PATCH 47/93] feat(GODT-2772): marked strings as translatable. --- .../qml/SetupWizard/ClientConfigAppleMail.qml | 10 +++++----- .../qml/SetupWizard/ClientConfigParameters.qml | 4 ++-- .../qml/SetupWizard/ClientConfigSelector.qml | 2 +- .../bridge-gui/qml/SetupWizard/SetupWizard.qml | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml index bdef4a75..10508170 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml @@ -90,7 +90,7 @@ Item { Layout.fillWidth: true colorScheme: wizard.colorScheme horizontalAlignment: Text.AlignHCenter - text: "Install the bridge certificate" + text: qsTr("Install the bridge certificate") type: Label.LabelType.Title wrapMode: Text.WordWrap } @@ -100,7 +100,7 @@ Item { color: colorScheme.text_weak colorScheme: wizard.colorScheme horizontalAlignment: Text.AlignHCenter - text: "After clicking on the button below, a system pop-up will ask you for your credential, please enter your macOS user credentials (not your Proton account’s) and validate." + text: qsTr("After clicking on the button below, a system pop-up will ask you for your credentials, please enter your macOS user credentials (not your Proton account’s) and validate.") type: Label.LabelType.Body wrapMode: Text.WordWrap } @@ -121,7 +121,7 @@ Item { colorScheme: wizard.colorScheme enabled: !certificateInstall.waitingForCert loading: certificateInstall.waitingForCert - text: "Install the certificate" + text: qsTr("Install the certificate") onClicked: { certificateInstall.waitingForCert = true; @@ -163,7 +163,7 @@ Item { Layout.fillWidth: true colorScheme: wizard.colorScheme horizontalAlignment: Text.AlignHCenter - text: "Install the profile" + text: qsTr("Install the profile") type: Label.LabelType.Title wrapMode: Text.WordWrap } @@ -192,7 +192,7 @@ Item { Button { Layout.fillWidth: true colorScheme: wizard.colorScheme - text: "Install the profile" + text: qsTr("Install the profile") onClicked: { wizard.user.configureAppleMail(wizard.address); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml index 366a55d3..91f5804a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml @@ -128,7 +128,7 @@ Rectangle { password: wizard.user ? wizard.user.password : "" port: Backend.imapPort.toString() security: Backend.useSSLForIMAP ? "SSL" : "STARTTLS" - title: qsTr("IMAP") + title: "IMAP" username: wizard.address } Configuration { @@ -139,7 +139,7 @@ Rectangle { password: wizard.user ? wizard.user.password : "" port: Backend.smtpPort.toString() security: Backend.useSSLForSMTP ? "SSL" : "STARTTLS" - title: qsTr("SMTP") + title: "SMTP" username: wizard.address } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml index 51aaf5ea..fcb5ddf6 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml @@ -77,7 +77,7 @@ Item { Layout.fillWidth: true colorScheme: wizard.colorScheme iconSource: "/qml/icons/ic-other-mail-clients.svg" - text: "Other" + text: qsTr("Other") onClicked: { wizard.client = SetupWizard.Client.Generic; diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 50ae4230..609b50d2 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -67,10 +67,10 @@ Item { case SetupWizard.Client.MozillaThunderbird: return "Thunderbird"; case SetupWizard.Client.Generic: - return "your email client"; + return qsTr("your email client"); default: console.error("Unknown mail client " + client); - return "your email client"; + return qsTr("your email client"); } } function closeWizard() { From e0875dc928fa2ee4089006fee8a4f81ea36dd916 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Thu, 24 Aug 2023 12:53:17 +0200 Subject: [PATCH 48/93] feat(GODT-2772): placement of error message on login pages. --- .../bridge-gui/qml/SetupWizard/Login.qml | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml index bc6d1f24..797a9863 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml @@ -128,20 +128,24 @@ FocusScope { Item { ColumnLayout { id: loginLayout + function clearErrors() { + usernameTextField.error = false; + usernameTextField.errorString = ""; + passwordTextField.error = false; + passwordTextField.errorString = ""; + errorLabel.text = "" + } function reset(clearUsername = false) { signInButton.loading = false; errorLabel.text = ""; usernameTextField.enabled = true; - usernameTextField.error = false; - usernameTextField.errorString = ""; usernameTextField.focus = true; if (clearUsername) { usernameTextField.text = ""; } passwordTextField.enabled = true; - passwordTextField.error = false; - passwordTextField.errorString = ""; passwordTextField.text = ""; + clearErrors(); } anchors.left: parent.left @@ -174,13 +178,12 @@ FocusScope { RowLayout { Layout.fillWidth: true spacing: 0 - visible: errorLabel.text.length > 0 - ColorImage { color: wizard.colorScheme.signal_danger height: errorLabel.lineHeight source: "/qml/icons/ic-exclamation-circle-filled.svg" sourceSize.height: errorLabel.lineHeight + visible: errorLabel.text.length > 0 } Label { id: errorLabel @@ -188,7 +191,7 @@ FocusScope { Layout.leftMargin: 4 color: wizard.colorScheme.signal_danger colorScheme: wizard.colorScheme - type: root.error ? Label.LabelType.Caption_semibold : Label.LabelType.Caption + type: Label.LabelType.Caption_semibold wrapMode: Text.WordWrap } } @@ -207,12 +210,7 @@ FocusScope { onAccepted: passwordTextField.forceActiveFocus() onTextChanged: { - // remove "invalid username / password error" - if (error || errorLabel.text.length > 0) { - errorLabel.text = ""; - usernameTextField.error = false; - passwordTextField.error = false; - } + loginLayout.clearErrors(); } } TextField { @@ -230,12 +228,7 @@ FocusScope { onAccepted: signInButton.checkAndSignIn() onTextChanged: { - // remove "invalid username / password error" - if (error || errorLabel.text.length > 0) { - errorLabel.text = ""; - usernameTextField.error = false; - passwordTextField.error = false; - } + loginLayout.clearErrors(); } } Button { From df09d6d2217e16e5f3fbc18b6e438f31fbc8a64e Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Thu, 24 Aug 2023 15:24:11 +0200 Subject: [PATCH 49/93] feat(GODT-2772): back button. --- .../bridge-gui/bridge-gui/Resources.qrc | 1 + .../bridge-gui/qml/Proton/Button.qml | 8 +++-- .../qml/SetupWizard/SetupWizard.qml | 31 +++++++++++++++++++ .../bridge-gui/qml/icons/ic-chevron-left.svg | 3 ++ 4 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-chevron-left.svg diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index aa184628..aeea603f 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -23,6 +23,7 @@ qml/icons/ic-card-identity.svg qml/icons/ic-check.svg qml/icons/ic-chevron-down.svg + qml/icons/ic-chevron-left.svg qml/icons/ic-chevron-right.svg qml/icons/ic-chevron-up.svg qml/icons/ic-cog-wheel.svg diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml index f505bca1..82817c76 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml @@ -23,14 +23,15 @@ T.Button { property bool borderless: false property ColorScheme colorScheme readonly property bool hasTextAndIcon: (control.text !== "") && (iconImage.source.toString().length > 0) + property bool iconOnTheLeft: false readonly property bool isIcon: control.text === "" property int labelType: Proton.Label.LabelType.Body property bool loading: false readonly property bool primary: !secondary property alias secondary: control.flat + property bool secondaryIsOpaque: false property alias textHorizontalAlignment: label.horizontalAlignment property alias textVerticalAlignment: label.verticalAlignment - property bool secondaryIsOpaque: false; font: label.font horizontalPadding: 16 @@ -78,7 +79,7 @@ T.Button { if (control.loading) { return control.colorScheme.interaction_default_hover; } - return secondaryIsOpaque ? control.colorScheme.background_norm: control.colorScheme.interaction_default; + return secondaryIsOpaque ? control.colorScheme.background_norm : control.colorScheme.interaction_default; } } else { if (primary) { @@ -116,6 +117,7 @@ T.Button { } contentItem: RowLayout { id: _contentItem + layoutDirection: iconOnTheLeft ? Qt.RightToLeft : Qt.LeftToRight spacing: control.hasTextAndIcon ? control.spacing : 0 Proton.Label { @@ -135,8 +137,8 @@ T.Button { opacity: control.enabled || control.loading ? 1.0 : 0.5 text: control.text type: labelType - visible: !control.isIcon verticalAlignment: Text.AlignVCenter + visible: !control.isIcon } ColorImage { id: iconImage diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 609b50d2..16ff35b0 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -36,6 +36,7 @@ Item { } property string address + property var backAction: null property int client property ColorScheme colorScheme property var user @@ -43,6 +44,9 @@ Item { signal showBugReport signal wizardEnded + function _showClientConfig() { + showClientConfig(root.user, root.address); + } function clientIconSource() { switch (client) { case SetupWizard.Client.AppleMail: @@ -77,11 +81,13 @@ Item { wizardEnded(); } function showAppleMailAutoConfig() { + backAction = _showClientConfig; rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigAppleMail; clientConfigAppleMail.showAutoconfig(); // This will trigger signals that will display the appropriate left content. } function showClientConfig(user, address) { + backAction = null; root.user = user; root.address = address; rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; @@ -89,12 +95,15 @@ Item { rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigSelector; } function showClientConfigEnd() { + backAction = null; rootStackLayout.currentIndex = SetupWizard.RootStack.ClientConfigEnd; } function showClientParams() { + backAction = _showClientConfig; rootStackLayout.currentIndex = SetupWizard.RootStack.ClientConfigParameters; } function showLogin(username = "") { + backAction = null; rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; root.address = ""; leftContent.showLogin(); @@ -103,6 +112,7 @@ Item { login.reset(false); } function showOnboarding() { + backAction = null; rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; root.address = ""; root.user = null; @@ -238,5 +248,26 @@ Item { HelpButton { wizard: root } + Button { + id: backButton + anchors.left: parent.left + anchors.leftMargin: 40 + anchors.top: parent.top + anchors.topMargin: 40 + colorScheme: root.colorScheme + icon.source: "/qml/icons/ic-chevron-left.svg" + iconOnTheLeft: true + secondary: true + secondaryIsOpaque: true + spacing: 8 + text: qsTr("Back") + visible: backAction != null + + onClicked: { + if (backAction) { + backAction(); + } + } + } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-chevron-left.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-chevron-left.svg new file mode 100644 index 00000000..13be3148 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-chevron-left.svg @@ -0,0 +1,3 @@ + + + From 958e1280d746fb9b63965509ca00afde36f90d7b Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Thu, 24 Aug 2023 18:27:12 +0200 Subject: [PATCH 50/93] feat(GODT-2772): error handling for Apple Mail auto config. --- .../bridge-gui-tester/GRPCService.cpp | 2 +- .../bridge-gui/bridge-gui/qml/MainWindow.qml | 2 +- .../qml/SetupWizard/ClientConfigAppleMail.qml | 69 ++++++++++++++++--- .../bridge-gui/qml/SetupWizard/HelpButton.qml | 5 +- .../qml/SetupWizard/SetupWizard.qml | 7 +- 5 files changed, 69 insertions(+), 16 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp index 4e8fb8f0..89a15e4f 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp @@ -794,7 +794,7 @@ Status GRPCService::InstallTLSCertificate(ServerContext *, Empty const *, Empty event = newCertificateInstallCanceledEvent(); break; default: - event = newCertificateInstallCanceledEvent(); + event = newCertificateInstallFailedEvent(); break; } qtProxy_.sendDelayedEvent(event); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index 38b739b4..78db7480 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -171,7 +171,7 @@ ApplicationWindow { Layout.fillWidth: true colorScheme: root.colorScheme - onShowBugReport: { + onBugReportRequested: { contentWrapper.showBugReport(); } onWizardEnded: { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml index 10508170..48a42e0f 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml @@ -30,19 +30,20 @@ Item { function showAutoconfig() { certificateInstall.waitingForCert = false; if (Backend.isTLSCertificateInstalled()) { - showCertificateInstall(); - } else { showProfileInstall(); + } else { + showCertificateInstall(); } } function showCertificateInstall() { - stack.currentIndex = ClientConfigAppleMail.Screen.ProfileInstall; - appleMailAutoconfigProfileInstallPageShow(); - } - function showProfileInstall() { + certificateInstall.reset(); stack.currentIndex = ClientConfigAppleMail.Screen.CertificateInstall; appleMailAutoconfigCertificateInstallPageShown(); } + function showProfileInstall() { + stack.currentIndex = ClientConfigAppleMail.Screen.ProfileInstall; + appleMailAutoconfigProfileInstallPageShow(); + } StackLayout { id: stack @@ -52,8 +53,19 @@ Item { Item { id: certificateInstall + property string errorString: "" + property bool showBugReportLink: false property bool waitingForCert: false + function clearError() { + errorString = ""; + showBugReportLink = false; + } + function reset() { + waitingForCert = false; + clearError(); + } + Layout.fillHeight: true Layout.fillWidth: true @@ -65,17 +77,17 @@ Item { Connections { function onCertificateInstallCanceled() { - // Note: this will lead to a warning message in the final version. certificateInstall.waitingForCert = false; - console.error("Certificate installation was canceled"); + certificateInstall.errorString = qsTr("Apple Mail cannot be configured if you do not install the certificate.Please retry."); + certificateInstall.showBugReportLink = false; } function onCertificateInstallFailed() { - // Note: this will lead to an error message in the final version. certificateInstall.waitingForCert = false; - console.error("Certificate installation failed"); + certificateInstall.errorString = qsTr("An error occurred while installing the certificate."); + certificateInstall.showBugReportLink = true; } function onCertificateInstallSuccess() { - certificateInstall.waitingForCert = false; + certificateInstall.reset(); root.showAutoconfig(); } @@ -124,6 +136,7 @@ Item { text: qsTr("Install the certificate") onClicked: { + certificateInstall.clearError(); certificateInstall.waitingForCert = true; Backend.installTLSCertificate(); } @@ -139,6 +152,40 @@ Item { wizard.closeWizard(); } } + ColumnLayout { + Layout.fillWidth: true + spacing: 8 + + RowLayout { + Layout.fillWidth: true + spacing: 4 + + ColorImage { + color: wizard.colorScheme.signal_danger + height: errorLabel.lineHeight + source: "/qml/icons/ic-exclamation-circle-filled.svg" + sourceSize.height: errorLabel.lineHeight + visible: certificateInstall.errorString.length > 0 + } + Label { + id: errorLabel + Layout.fillWidth: true + color: wizard.colorScheme.signal_danger + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: certificateInstall.errorString + type: Label.LabelType.Body_semibold + wrapMode: Text.WordWrap + } + } + LinkLabel { + Layout.alignment: Qt.AlignHCenter + colorScheme: wizard.colorScheme + callback: wizard.showBugReport + text: link("#", qsTr("Report the problem")) + visible: certificateInstall.showBugReportLink + } + } } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml index e897b11a..6370a8e8 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml @@ -47,7 +47,8 @@ Button { text: qsTr("Get help") onClicked: { - console.error("Get help"); + Backend.notifyKBArticleClicked("https://proton.me/support/bridge"); + Backend.showHelp(); } } MenuItem { @@ -56,7 +57,7 @@ Button { text: qsTr("Report a problem") onClicked: { - console.error("Report a problem"); + wizard.showBugReport(); } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 16ff35b0..1cc0d40d 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -41,9 +41,14 @@ Item { property ColorScheme colorScheme property var user - signal showBugReport + signal bugReportRequested signal wizardEnded + function showBugReport() { + closeWizard() + bugReportRequested() + } + function _showClientConfig() { showClientConfig(root.user, root.address); } From 32f2c72575d5da380b0c3359052493171895ed20 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Fri, 25 Aug 2023 09:39:43 +0200 Subject: [PATCH 51/93] feat(GODT-2772): use WebEngineView instead of WebView --- .../frontend/bridge-gui/bridge-gui/QMLBackend.h | 3 ++- .../bridge-gui/bridge-gui/Resources.qrc | 4 ++-- .../bridge-gui/bridge-gui/qml/Bridge.qml | 17 ++++++++++------- .../bridge-gui/bridge-gui/qml/HelpView.qml | 2 +- .../bridge-gui/bridge-gui/qml/MainWindow.qml | 12 ++++++------ .../qml/Proton/{WebView.qml => WebFrame.qml} | 10 +++++++--- .../bridge-gui/bridge-gui/qml/Proton/qmldir | 2 +- .../{WebViewWindow.qml => WebFrameWindow.qml} | 15 ++++++++++----- 8 files changed, 39 insertions(+), 26 deletions(-) rename internal/frontend/bridge-gui/bridge-gui/qml/Proton/{WebView.qml => WebFrame.qml} (92%) rename internal/frontend/bridge-gui/bridge-gui/qml/{WebViewWindow.qml => WebFrameWindow.qml} (89%) diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h index 83ae80b8..0220f15e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h @@ -277,7 +277,8 @@ signals: // Signals received from the Go backend, to be forwarded to QML void hideMainWindow(); ///< Signal for the 'hideMainWindow' gRPC stream event. void showHelp(); ///< Signal for the 'showHelp' event (from the context menu). void showSettings(); ///< Signal for the 'showHelp' event (from the context menu). - void showWebViewWindow(QString const &url); ///< Signal the the 'showWebViewWindow' event + void showWebFrameWindow(QString const &url); ///< Signal the the 'showWebFrameWindow' event + void showWebFrameOverlay(QString const &url); ////< Signal for the 'showWebFrameOverlay' event. void selectUser(QString const& userID, bool forceShowWindow); ///< Signal emitted in order to selected a user with a given ID in the list. void genericError(QString const &title, QString const &description); ///< Signal for the 'genericError' gRPC stream event. void imapLoginWhileSignedOut(QString const& username); ///< Signal for the notification of IMAP login attempt on a signed out account. diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index aeea603f..b79a8b40 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -107,7 +107,7 @@ qml/Proton/TextArea.qml qml/Proton/TextField.qml qml/Proton/Toggle.qml - qml/Proton/WebView.qml + qml/Proton/WebFrame.qml qml/QuestionItem.qml qml/Resources/bug_report_flow.json qml/SettingsItem.qml @@ -126,6 +126,6 @@ qml/ConnectionModeSettings.qml qml/SplashScreen.qml qml/Status.qml - qml/WebViewWindow.qml + qml/WebFrameWindow.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml index 8ee7c5e8..9e1f6d06 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml @@ -69,16 +69,19 @@ QtObject { Backend.setNormalTrayIcon(); } } - property WebViewWindow _webviewWindow: WebViewWindow { - id: webViewWindow - flags: Qt.Tool + property WebFrameWindow _webFrameWindow: WebFrameWindow { + id: webFrameWindow + colorScheme: ProtonStyle.currentStyle transientParent: mainWindow - visible: false + flags: Qt.Tool Connections { - function onShowWebViewWindow(url) { - webViewWindow.url = url; - webViewWindow.show(); + function onShowWebFrameWindow(url) { + webFrameWindow.url = url; + webFrameWindow.showNormal(); + } + function onShowWebFrameOverlay(url) { + mainWindow.showWebFrameOverlay(url) } target: Backend diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml b/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml index 4a604989..b27efd52 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml @@ -104,7 +104,7 @@ SettingsView { type: Label.Caption onLinkActivated: function (link) { - Backend.showWebViewWindow(link) + Backend.showWebFrameOverlay(link) } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index 78db7480..c67bacb1 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -57,7 +57,7 @@ ApplicationWindow { setupWizard.showClientConfig(user, address); } function showHelp() { - showWebViewOverlay("https://proton.me/support/bridge"); + Backend.showWebFrameWindow("https://proton.me/support/bridge"); } function showLocalCacheSettings() { contentWrapper.showLocalCacheSettings(); @@ -69,9 +69,9 @@ ApplicationWindow { function showSettings() { contentWrapper.showSettings(); } - function showWebViewOverlay(url) { - webViewOverlay.visible = true; - webViewOverlay.url = url; + function showWebFrameOverlay(url) { + webFrameOverlay.visible = true; + webFrameOverlay.url = url; } colorScheme: ProtonStyle.currentStyle @@ -179,8 +179,8 @@ ApplicationWindow { } } } - WebView { - id: webViewOverlay + WebFrame { + id: webFrameOverlay anchors.fill: parent colorScheme: root.colorScheme overlay: true diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebView.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml similarity index 92% rename from internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebView.qml rename to internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml index dc064edd..19948270 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebView.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml @@ -15,8 +15,8 @@ import QtQuick import QtQuick.Layouts import QtQuick.Controls import QtQuick.Controls.impl -import QtWebView -import "." as Proton +import QtWebEngine + Item { id: root @@ -55,11 +55,15 @@ Item { border.color: root.colorScheme.border_norm border.width: overlay ? ProtonStyle.web_view_overley_border_width : 0 - WebView { + WebEngineView { id: webView anchors.fill: parent anchors.margins: ProtonStyle.web_view_overley_border_width url: root.url + + onContextMenuRequested: function (request) { + request.accepted = true; // This prevent the default context menu from being presented. + } } } Button { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir index c866fc30..a13b58f2 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir @@ -37,4 +37,4 @@ Switch 4.0 Switch.qml TextArea 4.0 TextArea.qml TextField 4.0 TextField.qml Toggle 4.0 Toggle.qml -WebView 4.0 WebView.qml +WebFrame 4.0 WebFrame.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/WebViewWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/WebFrameWindow.qml similarity index 89% rename from internal/frontend/bridge-gui/bridge-gui/qml/WebViewWindow.qml rename to internal/frontend/bridge-gui/bridge-gui/qml/WebFrameWindow.qml index 12ecd3c3..597bcea6 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/WebViewWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/WebFrameWindow.qml @@ -17,13 +17,18 @@ import Proton Window { id: root - height: 600 - width: 800 - minimumWidth: 600 + property string url - WebView { + property ColorScheme colorScheme + + height: 600 + minimumWidth: 600 + width: 800 + + WebFrame { + id: frame anchors.fill: parent - colorScheme: ProtonStyle.currentStyle + colorScheme: root.colorScheme overlay: false url: root.url } From c849762445c08d72ff4e1757ec09f62dc802d1e0 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Fri, 25 Aug 2023 13:43:03 +0200 Subject: [PATCH 52/93] feat(GODT-2772): placeholder for missing help content. --- .../bridge-gui/bridge-gui/Resources.qrc | 1 + .../bridge-gui/bridge-gui/qml/Bridge.qml | 40 ++++++++++--------- .../bridge-gui/qml/Resources/ComingSoon.html | 28 +++++++++++++ .../SetupWizard/ClientConfigParameters.qml | 4 ++ .../bridge-gui/qml/SetupWizard/LeftPane.qml | 9 +++-- .../qml/SetupWizard/SetupWizard.qml | 21 +++++++--- 6 files changed, 77 insertions(+), 26 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/Resources/ComingSoon.html diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index b79a8b40..499de55d 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -110,6 +110,7 @@ qml/Proton/WebFrame.qml qml/QuestionItem.qml qml/Resources/bug_report_flow.json + qml/Resources/ComingSoon.html qml/SettingsItem.qml qml/SettingsView.qml qml/SetupWizard/ClientListItem.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml index 9e1f6d06..ab759e83 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml @@ -43,6 +43,28 @@ QtObject { target: Backend } + WebFrameWindow { + id: webFrameWindow + colorScheme: ProtonStyle.currentStyle + flags: Qt.Tool + transientParent: mainWindow + + Connections { + function onShowWebFrameOverlay(url) { + mainWindow.showWebFrameOverlay(url); + } + function onShowWebFrameWindow(url) { + webFrameWindow.url = url; + webFrameWindow.show(); + webFrameWindow.raise(); + if (!webFrameWindow.active) { + webFrameWindow.requestActivate(); + } + } + + target: Backend + } + } } property Notifications _notifications: Notifications { id: notifications @@ -69,24 +91,6 @@ QtObject { Backend.setNormalTrayIcon(); } } - property WebFrameWindow _webFrameWindow: WebFrameWindow { - id: webFrameWindow - colorScheme: ProtonStyle.currentStyle - transientParent: mainWindow - flags: Qt.Tool - - Connections { - function onShowWebFrameWindow(url) { - webFrameWindow.url = url; - webFrameWindow.showNormal(); - } - function onShowWebFrameOverlay(url) { - mainWindow.showWebFrameOverlay(url) - } - - target: Backend - } - } property var title: Backend.appname function bound(num, lowerLimit, upperLimit) { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Resources/ComingSoon.html b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/ComingSoon.html new file mode 100644 index 00000000..0539050e --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/ComingSoon.html @@ -0,0 +1,28 @@ + + + + + Coming soon + + + + +

+ The content of this page is under construction. +

+ + \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml index 91f5804a..704248fb 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml @@ -77,6 +77,10 @@ Rectangle { colorScheme: root.colorScheme icon.source: "/qml/icons/ic-external-link.svg" text: qsTr("Open Guide") + + onClicked: function () { + Backend.showWebFrameWindow(wizard.setupGuideLink()); + } } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 3bf09596..126c7a35 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -26,7 +26,7 @@ Item { function showAppleMailAutoconfigCertificateInstall() { showAppleMailAutoconfigCommon(); descriptionLabel.text = qsTr("Apple Mail configuration is mostly automated, but in order to work, Bridge needs to install a certificate in your keychain."); - linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why is this certificate needed?")); + linkLabel1.setCallback(showUnderConstruction, qsTr("Why is this certificate needed?")); } function showAppleMailAutoconfigCommon() { titleLabel.text = ""; @@ -39,7 +39,7 @@ Item { function showAppleMailAutoconfigProfileInstall() { showAppleMailAutoconfigCommon(); descriptionLabel.text = qsTr("The final step before you can start using Apple Mail is to install the Bridge server profile in the system preferences.\n\nAdding a server profile is necessary to ensure that your Mac can receive and send Proton Mails."); - linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why is there a yellow warning sign?")); + linkLabel1.setCallback(showUnderConstruction, qsTr("Why is there a yellow warning sign?")); linkLabel2.setCallback(wizard.showClientParams, qsTr("Configure Apple Mail manually")); } function showClientSelector() { @@ -63,12 +63,15 @@ Item { function showOnboarding() { titleLabel.text = (Backend.users.count === 0) ? qsTr("Welcome to\nProton Mail Bridge") : qsTr("Add a Proton Mail account"); descriptionLabel.text = qsTr("Bridge is the gateway between your Proton account and your email client. It runs in the background and encrypts and decrypts your messages seamlessly. "); - linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why do I need Bridge?")); + linkLabel1.setCallback(showUnderConstruction, qsTr("Why do I need Bridge?")); linkLabel2.clear(); root.iconSource = "/qml/icons/img-welcome.svg"; root.iconHeight = 148; root.iconWidth = 265; } + function showUnderConstruction() { + Backend.showWebFrameOverlay("qrc:/qml/Resources/ComingSoon.html"); + } Connections { function onLogin2FARequested() { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 1cc0d40d..5012b32c 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -44,11 +44,6 @@ Item { signal bugReportRequested signal wizardEnded - function showBugReport() { - closeWizard() - bugReportRequested() - } - function _showClientConfig() { showClientConfig(root.user, root.address); } @@ -85,12 +80,28 @@ Item { function closeWizard() { wizardEnded(); } + function setupGuideLink() { + switch (client) { + case SetupWizard.Client.AppleMail: + return "https://proton.me/support/protonmail-bridge-clients-apple-mail"; + case SetupWizard.Client.MicrosoftOutlook: + return (Backend.goos === "darwin") ? "https://proton.me/support/protonmail-bridge-clients-macos-outlook-2019" : "https://proton.me/support/protonmail-bridge-clients-windows-outlook-2019"; + case SetupWizard.Client.MozillaThunderbird: + return "https://proton.me/support/protonmail-bridge-clients-windows-thunderbird"; + default: + return "https://proton.me/support/protonmail-bridge-configure-client"; + } + } function showAppleMailAutoConfig() { backAction = _showClientConfig; rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigAppleMail; clientConfigAppleMail.showAutoconfig(); // This will trigger signals that will display the appropriate left content. } + function showBugReport() { + closeWizard(); + bugReportRequested(); + } function showClientConfig(user, address) { backAction = null; root.user = user; From 53f5f9aa43777bdee50c5a0070fa623345cc278e Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Fri, 25 Aug 2023 14:58:46 +0200 Subject: [PATCH 53/93] feat(GODT-2772): client selector left pane tweaks. --- .../frontend/bridge-gui/bridge-gui/qml/AccountView.qml | 6 +++--- .../bridge-gui/bridge-gui/qml/ContentWrapper.qml | 6 +++--- .../frontend/bridge-gui/bridge-gui/qml/MainWindow.qml | 10 +++++----- .../bridge-gui/bridge-gui/qml/Proton/WebFrame.qml | 9 ++------- .../bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml | 4 ++-- .../bridge-gui/qml/SetupWizard/SetupWizard.qml | 8 ++++---- 6 files changed, 19 insertions(+), 24 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml b/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml index cea82cb9..79e55e12 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml @@ -28,7 +28,7 @@ Item { property var notifications property var user - signal showClientConfigurator(var user, string address) + signal showClientConfigurator(var user, string address, bool justLoggedIn) signal showLogin(var username) Rectangle { @@ -129,7 +129,7 @@ Item { onClicked: { if (!root.user) return; - root.showClientConfigurator(root.user, user.addresses[0]); + root.showClientConfigurator(root.user, user.addresses[0], false); } } SettingsItem { @@ -171,7 +171,7 @@ Item { onClicked: { if (!root.user) return; - root.showClientConfigurator(root.user, addressSelector.displayText); + root.showClientConfigurator(root.user, addressSelector.displayText, false); } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml b/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml index 5cc343ab..4b7a7fa4 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml @@ -24,7 +24,7 @@ Item { signal closeWindow signal quitBridge - signal showClientConfigurator(var user, string address) + signal showClientConfigurator(var user, string address, bool justLoggedIn) signal showLogin(var username) function selectUser(userID) { @@ -336,8 +336,8 @@ Item { return Backend.users.get(accounts.currentIndex); } - onShowClientConfigurator: function (user, address) { - root.showClientConfigurator(user, address); + onShowClientConfigurator: function (user, address, justLoggedIn) { + root.showClientConfigurator(user, address, justLoggedIn); } onShowLogin: function (username) { root.showLogin(username); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index c67bacb1..274425cf 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -52,9 +52,9 @@ ApplicationWindow { root.requestActivate(); } } - function showClientConfigurator(user, address) { + function showClientConfigurator(user, address, justLoggedIn) { contentLayout.currentIndex = 1; - setupWizard.showClientConfig(user, address); + setupWizard.showClientConfig(user, address, justLoggedIn); } function showHelp() { Backend.showWebFrameWindow("https://proton.me/support/bridge"); @@ -103,7 +103,7 @@ ApplicationWindow { if (user.setupGuideSeen) { return; } - root.showClientConfigurator(user, user.addresses[0]); + root.showClientConfigurator(user, user.addresses[0], false); } target: Backend.users @@ -158,8 +158,8 @@ ApplicationWindow { root.close(); Backend.quit(); } - onShowClientConfigurator: function (user, address) { - root.showClientConfigurator(user, address); + onShowClientConfigurator: function (user, address, justLoggedIn) { + root.showClientConfigurator(user, address, justLoggedIn); } onShowLogin: function (username) { root.showLogin(username); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml index 19948270..5ba98e53 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml @@ -15,8 +15,7 @@ import QtQuick import QtQuick.Layouts import QtQuick.Controls import QtQuick.Controls.impl -import QtWebEngine - +import QtWebView Item { id: root @@ -55,15 +54,11 @@ Item { border.color: root.colorScheme.border_norm border.width: overlay ? ProtonStyle.web_view_overley_border_width : 0 - WebEngineView { + WebView { id: webView anchors.fill: parent anchors.margins: ProtonStyle.web_view_overley_border_width url: root.url - - onContextMenuRequested: function (request) { - request.accepted = true; // This prevent the default context menu from being presented. - } } } Button { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 126c7a35..20ee1aa2 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -42,9 +42,9 @@ Item { linkLabel1.setCallback(showUnderConstruction, qsTr("Why is there a yellow warning sign?")); linkLabel2.setCallback(wizard.showClientParams, qsTr("Configure Apple Mail manually")); } - function showClientSelector() { + function showClientSelector(newAccount = true) { titleLabel.text = ""; - descriptionLabel.text = qsTr("Bridge is now connected to Proton, and has already started downloading your messages. Let’s now connect your email client to Bridge."); + descriptionLabel.text = newAccount ? qsTr("Bridge is now connected to Proton, and has already started downloading your messages. Let’s now connect your email client to Bridge.") : qsTr("Let’s connect your email client to Bridge."); linkLabel1.clear(); linkLabel2.clear(); iconSource = "/qml/icons/img-client-config-selector.svg"; diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 5012b32c..d0085230 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -45,7 +45,7 @@ Item { signal wizardEnded function _showClientConfig() { - showClientConfig(root.user, root.address); + showClientConfig(root.user, root.address, false); } function clientIconSource() { switch (client) { @@ -102,12 +102,12 @@ Item { closeWizard(); bugReportRequested(); } - function showClientConfig(user, address) { + function showClientConfig(user, address, justLoggedIn) { backAction = null; root.user = user; root.address = address; rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; - leftContent.showClientSelector(); + leftContent.showClientSelector(justLoggedIn); rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigSelector; } function showClientConfigEnd() { @@ -144,7 +144,7 @@ Item { } let user = Backend.users.get(userIndex); let address = user ? user.addresses[0] : ""; - showClientConfig(user, address); + showClientConfig(user, address, true); } target: Backend From 86cd2437aaaebcc9bf6b5fe022237ee250f497a9 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Mon, 28 Aug 2023 17:45:06 +0200 Subject: [PATCH 54/93] feat(GODT-2772): misc tweaks. - Step description box tweaks and text color changes. - Factored out some constants (margins and dimensions. - Removed the ProtonStyle.px scaling which was useless as it was not applied everywhere. --- .../bridge-gui/qml/AccountDelegate.qml | 10 +-- .../bridge-gui/bridge-gui/qml/MainWindow.qml | 9 ++- .../bridge-gui/qml/Proton/Style.qml | 60 +++++++++++------- .../qml/SetupWizard/ClientConfigAppleMail.qml | 17 +++-- .../qml/SetupWizard/ClientConfigEnd.qml | 4 +- .../SetupWizard/ClientConfigParameters.qml | 18 +++--- .../qml/SetupWizard/ClientConfigSelector.qml | 4 +- .../qml/SetupWizard/ClientListItem.qml | 6 +- .../bridge-gui/qml/SetupWizard/HelpButton.qml | 10 +-- .../bridge-gui/qml/SetupWizard/LeftPane.qml | 2 +- .../bridge-gui/qml/SetupWizard/Login.qml | 12 ++-- .../bridge-gui/qml/SetupWizard/Onboarding.qml | 2 +- .../qml/SetupWizard/SetupWizard.qml | 24 +++---- .../qml/SetupWizard/StepDescriptionBox.qml | 63 +++++++++---------- 14 files changed, 124 insertions(+), 117 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/AccountDelegate.qml b/internal/frontend/bridge-gui/bridge-gui/qml/AccountDelegate.qml index 3f4bdfea..3b14bca6 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/AccountDelegate.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/AccountDelegate.qml @@ -22,7 +22,7 @@ Item { LargeView } - property var _spacing: 12 * ProtonStyle.px + property var _spacing: 12 property ColorScheme colorScheme property color progressColor: { if (!root.enabled) @@ -154,7 +154,7 @@ Item { } } Item { - implicitHeight: root.type === AccountDelegate.LargeView ? 6 * ProtonStyle.px : 0 + implicitHeight: root.type === AccountDelegate.LargeView ? 6 : 0 } RowLayout { spacing: 0 @@ -222,15 +222,15 @@ Item { } } Item { - implicitHeight: root.type === AccountDelegate.LargeView ? 3 * ProtonStyle.px : 0 + implicitHeight: root.type === AccountDelegate.LargeView ? 3 : 0 } Rectangle { id: progress_bar color: root.colorScheme.border_weak - height: 4 * ProtonStyle.px + height: 4 radius: ProtonStyle.progress_bar_radius visible: root.user ? root.type === AccountDelegate.LargeView : false - width: 140 * ProtonStyle.px + width: 140 Rectangle { id: progress_bar_filled diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index 274425cf..bd1451b4 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -22,8 +22,6 @@ import "SetupWizard" ApplicationWindow { id: root - property int _defaultHeight: 780 - property int _defaultWidth: 1080 property var notifications function layoutForUserCount(userCount) { @@ -75,10 +73,11 @@ ApplicationWindow { } colorScheme: ProtonStyle.currentStyle - height: _defaultHeight - minimumWidth: _defaultWidth + height: ProtonStyle.window_default_height + minimumHeight:ProtonStyle.window_minimum_height + minimumWidth: ProtonStyle.window_minimum_width visible: true - width: _defaultWidth + width: ProtonStyle.window_default_width Component.onCompleted: { layoutForUserCount(Backend.users.count); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml index bf1561ce..504bfdad 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml @@ -20,21 +20,21 @@ import "." QtObject { id: root - property real account_hover_radius: 12 * root.px // px - property real account_row_radius: 12 * root.px // px - property real avatar_radius: 8 * root.px // px - property real banner_radius: 12 * root.px // px - property real big_avatar_radius: 12 * root.px // px + property int account_hover_radius: 12 + property int account_row_radius: 12 + property int avatar_radius: 8 + property int banner_radius: 12 + property int big_avatar_radius: 12 property int body_font_size: 14 - property real body_letter_spacing: 0.2 * root.px + property real body_letter_spacing: 0.2 property int body_line_height: 20 - property real button_radius: 8 * root.px // px + property int button_radius: 8 property int caption_font_size: 12 - property real caption_letter_spacing: 0.4 * root.px + property real caption_letter_spacing: 0.4 property int caption_line_height: 16 - property real card_radius: 12 * root.px // px - property real checkbox_radius: 4 * root.px // px - property real context_item_radius: 8 * root.px // px + property int card_radius: 12 + property int checkbox_radius: 4 + property int context_item_radius: 8 property ColorScheme currentStyle: lightStyle property ColorScheme darkProminentStyle: ColorScheme { id: _darkProminentStyle @@ -178,7 +178,7 @@ QtObject { text_norm: "#FFFFFF" text_weak: "#A7A4B5" } - property real dialog_radius: 12 * root.px // px + property int dialog_radius: 12 property int fontWeight_100: Font.Thin property int fontWeight_200: Font.Light property int fontWeight_300: Font.ExtraLight @@ -202,7 +202,7 @@ QtObject { } property int heading_font_size: 28 property int heading_line_height: 36 - property real input_radius: 8 * root.px // px + property int input_radius: 8 property int lead_font_size: 18 property int lead_line_height: 26 property ColorScheme lightProminentStyle: ColorScheme { @@ -354,19 +354,33 @@ QtObject { text_norm: "#0C0C14" text_weak: "#706D6B" } - property real progress_bar_radius: 3 * root.px // px - property real px: 1.00 // px + property int progress_bar_radius: 3 property int title_font_size: 20 property int title_line_height: 24 - property real tooltip_radius: 8 * root.px // px + property int tooltip_radius: 8 // WebView overlay styling - property real web_view_button_width: 320 * root.px - property real web_view_corner_radius: 10 * root.px - property real web_view_overlay_button_vertical_margin: 10 * root.px - property real web_view_overlay_horizontal_margin: 10 * root.px - property real web_view_overlay_margin: 50 * root.px + property int web_view_button_width: 320 + property int web_view_corner_radius: 10 + property int web_view_overlay_button_vertical_margin: 10 + property int web_view_overlay_horizontal_margin: 10 + property int web_view_overlay_margin: 50 property real web_view_overlay_opacity: 0.6 - property real web_view_overlay_vertical_margin: web_view_corner_radius - property real web_view_overley_border_width: 1 * root.px + property int web_view_overlay_vertical_margin: web_view_corner_radius + property int web_view_overley_border_width: 1 + + property int window_default_height: 780 + property int window_default_width: 1080 + property int window_minimum_height: 650 + property int window_minimum_width: window_default_width + + // setup wizard constant + property int wizard_pane_bottomMargin: 92 + property int wizard_pane_width: 364 + property int wizard_window_margin: 40 + property int wizard_spacing_extra_large: 32 + property int wizard_spacing_extra_small: 4 + property int wizard_spacing_large: 24 + property int wizard_spacing_medium: 16 + property int wizard_spacing_small: 8 } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml index 48a42e0f..4ffacf34 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml @@ -73,7 +73,7 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - spacing: 24 + spacing: ProtonStyle.wizard_spacing_large Connections { function onCertificateInstallCanceled() { @@ -95,7 +95,7 @@ Item { } ColumnLayout { Layout.fillWidth: true - spacing: 16 + spacing: ProtonStyle.wizard_spacing_medium Label { Layout.alignment: Qt.AlignHCenter @@ -126,7 +126,7 @@ Item { } ColumnLayout { Layout.fillWidth: true - spacing: 16 + spacing: ProtonStyle.wizard_spacing_medium Button { Layout.fillWidth: true @@ -154,11 +154,11 @@ Item { } ColumnLayout { Layout.fillWidth: true - spacing: 8 + spacing: ProtonStyle.wizard_spacing_small RowLayout { Layout.fillWidth: true - spacing: 4 + spacing: ProtonStyle.wizard_spacing_extra_small ColorImage { color: wizard.colorScheme.signal_danger @@ -199,11 +199,11 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - spacing: 24 + spacing: ProtonStyle.wizard_spacing_large ColumnLayout { Layout.fillWidth: true - spacing: 16 + spacing: ProtonStyle.wizard_spacing_medium Label { Layout.alignment: Qt.AlignHCenter @@ -228,13 +228,12 @@ Item { Image { Layout.alignment: Qt.AlignHCenter height: 102 - opacity: certificateInstall.waitingForCert ? 0.3 : 1.0 source: "/qml/icons/img-macos-profile-screenshot.png" width: 364 } ColumnLayout { Layout.fillWidth: true - spacing: 16 + spacing: ProtonStyle.wizard_spacing_medium Button { Layout.fillWidth: true diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigEnd.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigEnd.qml index 3893f7e4..7302fb6f 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigEnd.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigEnd.qml @@ -32,13 +32,13 @@ Rectangle { anchors.top: parent.top anchors.topMargin: 32 clip: true - width: 364 + width: ProtonStyle.wizard_pane_width ColumnLayout { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - spacing: 16 + spacing: ProtonStyle.wizard_spacing_medium Image { Layout.alignment: Qt.AlignHCenter diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml index 704248fb..30c65227 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml @@ -37,7 +37,7 @@ Rectangle { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - spacing: 16 + spacing: ProtonStyle.wizard_spacing_medium Label { Layout.alignment: Qt.AlignHCenter @@ -53,15 +53,15 @@ Rectangle { border.color: colorScheme.border_norm border.width: 1 color: "transparent" - height: childrenRect.height + 2 * 16 + height: childrenRect.height + 2 * ProtonStyle.wizard_spacing_medium radius: 12 RowLayout { anchors.left: parent.left - anchors.margins: 16 + anchors.margins: ProtonStyle.wizard_spacing_medium anchors.right: parent.right anchors.top: parent.top - spacing: 8 + spacing: ProtonStyle.wizard_spacing_small Label { Layout.fillHeight: true @@ -89,15 +89,15 @@ Rectangle { border.color: colorScheme.signal_warning border.width: 1 color: "transparent" - height: childrenRect.height + 2 * 16 - radius: 12 + height: childrenRect.height + 2 * ProtonStyle.wizard_spacing_medium + radius: ProtonStyle.banner_radius RowLayout { anchors.left: parent.left - anchors.margins: 16 + anchors.margins: ProtonStyle.wizard_spacing_medium anchors.right: parent.right anchors.top: parent.top - spacing: 16 + spacing: ProtonStyle.wizard_spacing_medium ColorImage { id: image @@ -122,7 +122,7 @@ Rectangle { RowLayout { id: configuration Layout.fillWidth: true - spacing: 32 + spacing: ProtonStyle.wizard_spacing_extra_large Configuration { Layout.fillWidth: true diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml index fcb5ddf6..de062f35 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml @@ -26,12 +26,12 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - spacing: 16 + spacing: ProtonStyle.wizard_spacing_medium Label { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - Layout.topMargin: 16 + Layout.topMargin: ProtonStyle.wizard_spacing_medium colorScheme: wizard.colorScheme horizontalAlignment: Qt.AlignHCenter text: qsTr("Select your email client") diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientListItem.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientListItem.qml index 8a133605..1ffe6157 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientListItem.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientListItem.qml @@ -36,14 +36,14 @@ Rectangle { return colorScheme.background_norm; } height: 68 - radius: 12 + radius: ProtonStyle.banner_radius RowLayout { anchors.fill: parent - anchors.margins: 16 + anchors.margins: ProtonStyle.wizard_spacing_medium ColorImage { - height: 36 + height: sourceSize.height source: iconSource sourceSize.height: 36 } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml index 6370a8e8..7dcf4e80 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml @@ -19,17 +19,19 @@ Button { id: root property var wizard + readonly property int _iconPadding: 8 // The SVG image we use has internal padding that we need to compensate for alignment. + readonly property int _iconSize: 24 anchors.bottom: parent.bottom - anchors.bottomMargin: 32 + anchors.bottomMargin: ProtonStyle.wizard_window_margin - _iconPadding anchors.right: parent.right - anchors.rightMargin: 32 + anchors.rightMargin: ProtonStyle.wizard_window_margin - _iconPadding colorScheme: wizard.colorScheme horizontalPadding: 0 icon.color: wizard.colorScheme.text_weak - icon.height: 24 + icon.height: _iconSize icon.source: "/qml/icons/ic-question-circle.svg" - icon.width: 24 + icon.width: _iconSize verticalPadding: 0 onClicked: { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 20ee1aa2..38dbba1f 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -87,7 +87,7 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - spacing: 16 + spacing: ProtonStyle.wizard_spacing_medium Image { id: icon diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml index 797a9863..d6a67121 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml @@ -151,11 +151,11 @@ FocusScope { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - spacing: 16 + spacing: ProtonStyle.wizard_spacing_medium ColumnLayout { Layout.fillWidth: true - spacing: 8 + spacing: ProtonStyle.wizard_spacing_small Label { Layout.alignment: Qt.AlignHCenter @@ -287,11 +287,11 @@ FocusScope { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - spacing: 16 + spacing: ProtonStyle.wizard_spacing_medium ColumnLayout { Layout.fillWidth: true - spacing: 8 + spacing: ProtonStyle.wizard_spacing_small Label { Layout.alignment: Qt.AlignHCenter @@ -388,11 +388,11 @@ FocusScope { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - spacing: 16 + spacing: ProtonStyle.wizard_spacing_medium ColumnLayout { Layout.fillWidth: true - spacing: 8 + spacing: ProtonStyle.wizard_spacing_small Label { Layout.alignment: Qt.AlignHCenter diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml index 22bf5008..e3ea88ae 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml @@ -24,7 +24,7 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - spacing: 24 + spacing: ProtonStyle.wizard_spacing_large StepDescriptionBox { colorScheme: wizard.colorScheme diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index d0085230..3248184a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -168,12 +168,12 @@ Item { LeftPane { id: leftContent anchors.bottom: parent.bottom - anchors.bottomMargin: 92 + anchors.bottomMargin: ProtonStyle.wizard_pane_bottomMargin anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top - anchors.topMargin: 40 + anchors.topMargin: ProtonStyle.wizard_window_margin clip: true - width: 364 + width: ProtonStyle.wizard_pane_width wizard: root Connections { @@ -190,13 +190,13 @@ Item { Image { id: mailLogoWithWordmark anchors.bottom: parent.bottom - anchors.bottomMargin: 40 + anchors.bottomMargin: ProtonStyle.wizard_window_margin anchors.horizontalCenter: parent.horizontalCenter - height: 36 + height: sourceSize.height source: root.colorScheme.mail_logo_with_wordmark sourceSize.height: 36 sourceSize.width: 134 - width: 134 + width: sourceSize.width } } Rectangle { @@ -208,13 +208,13 @@ Item { StackLayout { id: rightContent anchors.bottom: parent.bottom - anchors.bottomMargin: 92 + anchors.bottomMargin: ProtonStyle.wizard_pane_bottomMargin anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top - anchors.topMargin: 40 + anchors.topMargin: ProtonStyle.wizard_window_margin clip: true currentIndex: 0 - width: 364 + width: ProtonStyle.wizard_pane_width // rightContent stack index 0 Onboarding { @@ -267,15 +267,15 @@ Item { Button { id: backButton anchors.left: parent.left - anchors.leftMargin: 40 + anchors.leftMargin: ProtonStyle.wizard_window_margin anchors.top: parent.top - anchors.topMargin: 40 + anchors.topMargin: ProtonStyle.wizard_window_margin colorScheme: root.colorScheme icon.source: "/qml/icons/ic-chevron-left.svg" iconOnTheLeft: true secondary: true secondaryIsOpaque: true - spacing: 8 + spacing: ProtonStyle.wizard_spacing_small text: qsTr("Back") visible: backAction != null diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml index 74e8c02d..375a279f 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml @@ -15,7 +15,7 @@ import QtQuick import QtQuick.Layouts import QtQuick.Controls -Item { +RowLayout { id: root property ColorScheme colorScheme @@ -24,43 +24,36 @@ Item { property int iconSize: 64 property string title - implicitHeight: children[0].implicitHeight - implicitWidth: children[0].implicitWidth + spacing: ProtonStyle.wizard_spacing_large - RowLayout { - anchors.fill: parent - spacing: 24 + Image { + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + Layout.preferredHeight: iconSize + Layout.preferredWidth: iconSize + mipmap: true + source: root.icon + } + ColumnLayout { + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Layout.fillWidth: true + spacing: ProtonStyle.wizard_spacing_small - Image { - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - Layout.preferredHeight: iconSize - Layout.preferredWidth: iconSize - mipmap: true - source: root.icon - } - ColumnLayout { - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Label { + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.fillHeight: false Layout.fillWidth: true - spacing: 8 - - Label { - Layout.alignment: Qt.AlignLeft | Qt.AlignTop - Layout.fillHeight: false - Layout.fillWidth: true - colorScheme: root.colorScheme - text: root.title - type: Label.LabelType.Body_bold - } - Label { - Layout.alignment: Qt.AlignLeft | Qt.AlignTop - Layout.fillHeight: true - Layout.fillWidth: true - color: root.colorScheme.text_weak - colorScheme: root.colorScheme - text: root.description - type: Label.LabelType.Body - verticalAlignment: Text.AlignTop - } + colorScheme: root.colorScheme + text: root.title + type: Label.LabelType.Body_bold + } + Label { + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.fillHeight: true + Layout.fillWidth: true + colorScheme: root.colorScheme + text: root.description + type: Label.LabelType.Body + verticalAlignment: Text.AlignTop } } } From 0c7e17701f19b84ab318f6ace7673490f4e56e8f Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Mon, 28 Aug 2023 18:21:06 +0200 Subject: [PATCH 55/93] feat(GODT-2772): HTML placeholder is not loaded from resources anymore. --- .../bridge-gui/bridge-gui/Resources.qrc | 1 - .../bridge-gui/bridge-gui/qml/MainWindow.qml | 3 ++ .../bridge-gui/qml/Proton/WebFrame.qml | 12 +++++++- .../bridge-gui/qml/Resources/ComingSoon.html | 28 ------------------- .../bridge-gui/qml/SetupWizard/LeftPane.qml | 2 +- .../qml/SetupWizard/SetupWizard.qml | 1 + 6 files changed, 16 insertions(+), 31 deletions(-) delete mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/Resources/ComingSoon.html diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index 499de55d..b79a8b40 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -110,7 +110,6 @@ qml/Proton/WebFrame.qml qml/QuestionItem.qml qml/Resources/bug_report_flow.json - qml/Resources/ComingSoon.html qml/SettingsItem.qml qml/SettingsView.qml qml/SetupWizard/ClientListItem.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index bd1451b4..ea0763d4 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -176,6 +176,9 @@ ApplicationWindow { onWizardEnded: { contentLayout.currentIndex = 0; } + onShowUnderConstruction: { + webFrameOverlay.showUnderConstruction(); + } } } WebFrame { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml index 5ba98e53..a8f5812d 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml @@ -25,7 +25,17 @@ Item { property string url: "" function showBlankPage() { - webView.loadHtml("blank", "blank.html"); + webView.loadHtml("blank", ""); + } + + function showUnderConstruction() { + webView.loadHtml(` + + +Coming soon +

The content of this page is under construction.

+ `, "") + root.visible = true; } Rectangle { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Resources/ComingSoon.html b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/ComingSoon.html deleted file mode 100644 index 0539050e..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Resources/ComingSoon.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - Coming soon - - - - -

- The content of this page is under construction. -

- - \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 38dbba1f..563c8cb4 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -70,7 +70,7 @@ Item { root.iconWidth = 265; } function showUnderConstruction() { - Backend.showWebFrameOverlay("qrc:/qml/Resources/ComingSoon.html"); + wizard.showUnderConstruction(); } Connections { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 3248184a..060d3b93 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -43,6 +43,7 @@ Item { signal bugReportRequested signal wizardEnded + signal showUnderConstruction function _showClientConfig() { showClientConfig(root.user, root.address, false); From 36651698cbc7e0fcb600b50986783d8aa1a5e7d0 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Wed, 30 Aug 2023 08:00:09 +0200 Subject: [PATCH 56/93] feat(GODT-2772): new illustration for client selector. --- .../bridge-gui/qml/SetupWizard/LeftPane.qml | 4 +- .../qml/icons/img-client-config-selector.svg | 156 +++++++++++++++--- 2 files changed, 132 insertions(+), 28 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 563c8cb4..c452c520 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -48,8 +48,8 @@ Item { linkLabel1.clear(); linkLabel2.clear(); iconSource = "/qml/icons/img-client-config-selector.svg"; - iconHeight = 222; - iconWidth = 264; + iconHeight = 104; + iconWidth = 266; } function showLogin() { showOnboarding(); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-client-config-selector.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-client-config-selector.svg index 5b107446..0213b922 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-client-config-selector.svg +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-client-config-selector.svg @@ -1,19 +1,75 @@ - - - - - - - - - + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + @@ -21,27 +77,75 @@ - + - - - + + + + + + + + + + + - - - + + + - - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + From 683458e264a16a86a7172dca16b6dc91445b8d51 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Wed, 30 Aug 2023 08:19:57 +0200 Subject: [PATCH 57/93] feat(GODT-2772): use new Thunderbird logo. The logo is a raster image inside a SVG file, as the pure vector version does not render properly in QML or Affinity Designer. --- .../qml/icons/ic-mozilla-thunderbird.svg | 120 ++---------------- .../bridge-gui/qml/icons/img-mail-clients.svg | 51 +------- 2 files changed, 14 insertions(+), 157 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-mozilla-thunderbird.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-mozilla-thunderbird.svg index 83759ef0..8489b92d 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-mozilla-thunderbird.svg +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-mozilla-thunderbird.svg @@ -1,112 +1,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-clients.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-clients.svg index 09e14678..52925e21 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-clients.svg +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-clients.svg @@ -1,7 +1,7 @@ - - + + @@ -28,53 +28,12 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - + From 44df3cfd4a5d96c160b0925885d6ddddcfbc3d35 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Wed, 30 Aug 2023 09:38:26 +0200 Subject: [PATCH 58/93] feat(GODT-2772): configure email client button is highlighted Misc minor tweaks & fixes. --- .../bridge-gui/bridge-gui/qml/AccountView.qml | 11 ++++++----- .../qml/SetupWizard/ClientConfigParameters.qml | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml b/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml index 79e55e12..d5469435 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml @@ -23,6 +23,7 @@ Item { property int _detailsMargin: 25 property int _lineThickness: 1 property int _spacing: 20 + property int _buttonSpacing: 8 property int _topMargin: 32 property ColorScheme colorScheme property var notifications @@ -63,7 +64,7 @@ Item { // account delegate with action buttons Layout.fillWidth: true Layout.topMargin: _topMargin - + spacing: _buttonSpacing AccountDelegate { Layout.fillWidth: true colorScheme: root.colorScheme @@ -118,12 +119,12 @@ Item { } SettingsItem { Layout.fillWidth: true - actionText: qsTr("Configure") + actionText: qsTr("Configure email client") colorScheme: root.colorScheme description: qsTr("Using the mailbox details below (re)configure your client.") showSeparator: splitMode.visible text: qsTr("Email clients") - type: SettingsItem.Button + type: SettingsItem.PrimaryButton visible: _connected && ((!root.user.splitMode) || (root.user.addresses.length === 1)) onClicked: { @@ -165,8 +166,8 @@ Item { } Button { colorScheme: root.colorScheme - secondary: true - text: qsTr("Configure") + secondary: false + text: qsTr("Configure email client") onClicked: { if (!root.user) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml index 30c65227..709b692c 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml @@ -76,7 +76,7 @@ Rectangle { Button { colorScheme: root.colorScheme icon.source: "/qml/icons/ic-external-link.svg" - text: qsTr("Open Guide") + text: qsTr("Open guide") onClicked: function () { Backend.showWebFrameWindow(wizard.setupGuideLink()); From 8d346ea511db28f2b899735cb88f2e417fe841f7 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Thu, 31 Aug 2023 08:37:29 +0200 Subject: [PATCH 59/93] feat(GODT-2772): removed useless extra space in button with icons. --- internal/frontend/bridge-gui/bridge-gui/qml/SettingsItem.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SettingsItem.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SettingsItem.qml index ea0e6fc0..ecfa7621 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SettingsItem.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SettingsItem.qml @@ -90,7 +90,7 @@ Item { icon.source: root.actionIcon loading: root.loading secondary: root.type !== SettingsItem.PrimaryButton - text: root.actionText + (root.actionIcon !== "" ? " " : "") + text: root.actionText visible: root.type === SettingsItem.Button || root.type === SettingsItem.PrimaryButton onClicked: { From ea26dc0e971078620ff44c108617aa0b71398365 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Thu, 31 Aug 2023 10:24:09 +0200 Subject: [PATCH 60/93] feat(GODT-2772): external links have an icon. --- .../qml/Notifications/Notifications.qml | 2 +- .../bridge-gui/qml/Proton/LinkLabel.qml | 70 ++++++++++++++----- .../qml/SetupWizard/ClientConfigAppleMail.qml | 5 +- .../bridge-gui/qml/SetupWizard/LeftPane.qml | 8 +-- .../bridge-gui/qml/SetupWizard/Login.qml | 7 +- 5 files changed, 67 insertions(+), 25 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notifications.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notifications.qml index 94fcc9e6..04b2398d 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notifications.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notifications.qml @@ -453,7 +453,7 @@ QtObject { brief: title description: qsTr("Changing between split and combined address mode will require you to delete your account(s) from your email client and begin the setup process from scratch.") group: Notifications.Group.Configuration | Notifications.Group.Dialogs - icon: "/qml/icons/ic-question-circle.svg" + icon: "./icons/ic-question-circle.svg" title: qsTr("Enable split mode?") type: Notification.NotificationType.Warning diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/LinkLabel.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/LinkLabel.qml index 2b1feefd..49db82ed 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/LinkLabel.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/LinkLabel.qml @@ -12,39 +12,77 @@ // along with Proton Mail Bridge. If not, see . import QtQuick import QtQuick.Controls +import QtQuick.Layouts -Label { +RowLayout { id: root property var callback: null + property ColorScheme colorScheme + property bool external: false + property string link: "#" + property string text: "" function clear() { - callback = null; - text = ""; + root.callback = null; + root.text = ""; + root.link = ""; + root.external = false; } - function setCallback(callback, linkText) { + function link(url, text) { + return label.link(url, text); + } + function setCallback(callback, linkText, external) { root.callback = callback; - text = link("#", linkText); + root.text = linkText; + root.link = "#"; // Cannot be empty, otherwise the text is not an hyperlink. + root.external = external; } - function setLink(linkURL, linkText) { - callback = null; - text = link(linkURL, linkText); + function setLink(linkURL, linkText, external) { + root.callback = null; + root.text = linkText; + root.link = linkURL; + root.external = external; } - type: Label.LabelType.Body + Label { + id: label + Layout.alignment: Qt.AlignVCenter + colorScheme: root.colorScheme + text: label.link(root.link, root.text) + type: Label.LabelType.Body - onLinkActivated: function (link) { - if (link !== "#") { - Qt.openUrlExternally(link); - } - if (callback) { - callback(); + onLinkActivated: function (link) { + if ((link !== "#") && (link.length > 0)) { + Qt.openUrlExternally(link); + } + if (callback) { + callback(); + } } } + ColorImage { + Layout.alignment: Qt.AlignVCenter + color: label.linkColor + height: sourceSize.height + source: "/qml/icons/ic-external-link.svg" + sourceSize.height: 16 + sourceSize.width: 16 + visible: external + width: sourceSize.width + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + + onClicked: { + label.onLinkActivated(root.link); + } + } + } HoverHandler { acceptedDevices: PointerDevice.Mouse cursorShape: Qt.PointingHandCursor enabled: true } -} +} \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml index 4ffacf34..d7516a3b 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml @@ -180,9 +180,10 @@ Item { } LinkLabel { Layout.alignment: Qt.AlignHCenter - colorScheme: wizard.colorScheme callback: wizard.showBugReport - text: link("#", qsTr("Report the problem")) + colorScheme: wizard.colorScheme + link: "#" + text: qsTr("Report the problem") visible: certificateInstall.showBugReportLink } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index c452c520..88cf9e27 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -26,7 +26,7 @@ Item { function showAppleMailAutoconfigCertificateInstall() { showAppleMailAutoconfigCommon(); descriptionLabel.text = qsTr("Apple Mail configuration is mostly automated, but in order to work, Bridge needs to install a certificate in your keychain."); - linkLabel1.setCallback(showUnderConstruction, qsTr("Why is this certificate needed?")); + linkLabel1.setCallback(showUnderConstruction, qsTr("Why is this certificate needed?"), false); } function showAppleMailAutoconfigCommon() { titleLabel.text = ""; @@ -39,8 +39,8 @@ Item { function showAppleMailAutoconfigProfileInstall() { showAppleMailAutoconfigCommon(); descriptionLabel.text = qsTr("The final step before you can start using Apple Mail is to install the Bridge server profile in the system preferences.\n\nAdding a server profile is necessary to ensure that your Mac can receive and send Proton Mails."); - linkLabel1.setCallback(showUnderConstruction, qsTr("Why is there a yellow warning sign?")); - linkLabel2.setCallback(wizard.showClientParams, qsTr("Configure Apple Mail manually")); + linkLabel1.setCallback(showUnderConstruction, qsTr("Why is there a yellow warning sign?"), false); + linkLabel2.setCallback(wizard.showClientParams, qsTr("Configure Apple Mail manually"), false); } function showClientSelector(newAccount = true) { titleLabel.text = ""; @@ -63,7 +63,7 @@ Item { function showOnboarding() { titleLabel.text = (Backend.users.count === 0) ? qsTr("Welcome to\nProton Mail Bridge") : qsTr("Add a Proton Mail account"); descriptionLabel.text = qsTr("Bridge is the gateway between your Proton account and your email client. It runs in the background and encrypts and decrypts your messages seamlessly. "); - linkLabel1.setCallback(showUnderConstruction, qsTr("Why do I need Bridge?")); + linkLabel1.setCallback(showUnderConstruction, qsTr("Why do I need Bridge?"), false); linkLabel2.clear(); root.iconSource = "/qml/icons/img-welcome.svg"; root.iconHeight = 148; diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml index d6a67121..75cd78e0 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml @@ -133,7 +133,7 @@ FocusScope { usernameTextField.errorString = ""; passwordTextField.error = false; passwordTextField.errorString = ""; - errorLabel.text = "" + errorLabel.text = ""; } function reset(clearUsername = false) { signInButton.loading = false; @@ -178,6 +178,7 @@ FocusScope { RowLayout { Layout.fillWidth: true spacing: 0 + ColorImage { color: wizard.colorScheme.signal_danger height: errorLabel.lineHeight @@ -269,7 +270,9 @@ FocusScope { LinkLabel { Layout.alignment: Qt.AlignHCenter colorScheme: wizard.colorScheme - text: link("https://proton.me/mail/pricing", qsTr("Create or upgrade your account")) + external: true + link: "https://proton.me/mail/pricing" + text: qsTr("Create or upgrade your account") } } } From 5d4f8f7d40feed388afaef8484a5da88610c9af8 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Fri, 1 Sep 2023 08:43:05 +0200 Subject: [PATCH 61/93] feat(GODT-2772): implemented internal help links. --- .../bridge-gui/bridge-gui/QMLBackend.cpp | 22 +++++++++++++++++++ .../bridge-gui/bridge-gui/QMLBackend.h | 2 ++ .../bridge-gui/bridge-gui/Resources.qrc | 4 ++++ .../bridge-gui/bridge-gui/qml/Bridge.qml | 3 +++ .../bridge-gui/bridge-gui/qml/MainWindow.qml | 8 ++++--- .../bridge-gui/qml/Proton/Style.qml | 7 +++--- .../bridge-gui/qml/Proton/WebFrame.qml | 22 ++++++++----------- .../qml/Resources/Help/Template.html | 16 ++++++++++++++ .../qml/Resources/Help/WhyBridge.html | 19 ++++++++++++++++ .../qml/Resources/Help/WhyCertificate.html | 19 ++++++++++++++++ .../qml/Resources/Help/WhyProfileWarning.html | 21 ++++++++++++++++++ .../qml/SetupWizard/ClientConfigAppleMail.qml | 19 ++++++++++++---- .../bridge-gui/qml/SetupWizard/LeftPane.qml | 9 +++----- .../qml/SetupWizard/SetupWizard.qml | 1 - 14 files changed, 142 insertions(+), 30 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/Template.html create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyBridge.html create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyCertificate.html create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyProfileWarning.html diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp index 3bdd3012..8631a823 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp @@ -1099,6 +1099,28 @@ void QMLBackend::setUpdateTrayIcon(QString const &stateString, QString const &st } +//**************************************************************************************************************************************************** +/// \param[in] helpFileName The name of the help file with extension (e.g. "WhyBridge.html"). +//**************************************************************************************************************************************************** +void QMLBackend::showHelpOverlay(QString const &helpFileName) { + QDir const basePath(":/qml/Resources/Help"); + QString const templatePath = basePath.filePath("Template.html"); + QFile templateFile(templatePath); + if (!templateFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + app().log().error("Could not load help overlay HTML template"); + return; + } + + QFile helpFile(basePath.filePath(helpFileName)); + if (!helpFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + app().log().error(QString("Could not load help overlay HTML file %1").arg(helpFileName)); + return; + } + + emit showWebFrameOverlayHTML(QString::fromUtf8(templateFile.readAll()).arg(QString::fromUtf8(helpFile.readAll()))); +} + + //**************************************************************************************************************************************************** /// \param[in] isOn Does bridge consider internet as on. //**************************************************************************************************************************************************** diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h index 0220f15e..035fbca8 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h @@ -211,6 +211,7 @@ public slots: // slots for functions that need to be processed locally. void setErrorTrayIcon(QString const& stateString, QString const &statusIcon); ///< Set the tray icon to 'error' state. void setWarnTrayIcon(QString const& stateString, QString const &statusIcon); ///< Set the tray icon to 'warn' state. void setUpdateTrayIcon(QString const& stateString, QString const &statusIcon); ///< Set the tray icon to 'update' state. + void showHelpOverlay(QString const &helpFileName); ///< Slot triggering the display of help content as an overlay. public slots: // slot for signals received from gRPC that need transformation instead of simple forwarding void internetStatusChanged(bool isOn); ///< Check if bridge considers internet as on. @@ -279,6 +280,7 @@ signals: // Signals received from the Go backend, to be forwarded to QML void showSettings(); ///< Signal for the 'showHelp' event (from the context menu). void showWebFrameWindow(QString const &url); ///< Signal the the 'showWebFrameWindow' event void showWebFrameOverlay(QString const &url); ////< Signal for the 'showWebFrameOverlay' event. + void showWebFrameOverlayHTML(QString const &html); ///< Signal to display HTML content in a web frame overlay. void selectUser(QString const& userID, bool forceShowWindow); ///< Signal emitted in order to selected a user with a given ID in the list. void genericError(QString const &title, QString const &description); ///< Signal for the 'genericError' gRPC stream event. void imapLoginWhileSignedOut(QString const& username); ///< Signal for the notification of IMAP login attempt on a signed out account. diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index b79a8b40..dccfa1ca 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -110,6 +110,10 @@ qml/Proton/WebFrame.qml qml/QuestionItem.qml qml/Resources/bug_report_flow.json + qml/Resources/Help/Template.html + qml/Resources/Help/WhyBridge.html + qml/Resources/Help/WhyCertificate.html + qml/Resources/Help/WhyProfileWarning.html qml/SettingsItem.qml qml/SettingsView.qml qml/SetupWizard/ClientListItem.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml index ab759e83..96a9e5d7 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml @@ -53,6 +53,9 @@ QtObject { function onShowWebFrameOverlay(url) { mainWindow.showWebFrameOverlay(url); } + function onShowWebFrameOverlayHTML(html) { + mainWindow.showWebFrameOverlayHTML(html) + } function onShowWebFrameWindow(url) { webFrameWindow.url = url; webFrameWindow.show(); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index ea0763d4..575981f2 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -72,6 +72,11 @@ ApplicationWindow { webFrameOverlay.url = url; } + function showWebFrameOverlayHTML(html) { + webFrameOverlay.loadHTML(html); + webFrameOverlay.visible = true; + } + colorScheme: ProtonStyle.currentStyle height: ProtonStyle.window_default_height minimumHeight:ProtonStyle.window_minimum_height @@ -176,9 +181,6 @@ ApplicationWindow { onWizardEnded: { contentLayout.currentIndex = 0; } - onShowUnderConstruction: { - webFrameOverlay.showUnderConstruction(); - } } } WebFrame { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml index 504bfdad..95c6d431 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml @@ -363,10 +363,11 @@ QtObject { property int web_view_button_width: 320 property int web_view_corner_radius: 10 property int web_view_overlay_button_vertical_margin: 10 - property int web_view_overlay_horizontal_margin: 10 - property int web_view_overlay_margin: 50 + property int web_view_overlay_horizontal_padding: 10 + property int web_view_overlay_horizontal_margin: 250 + property int web_view_overlay_vertical_margin: 50 property real web_view_overlay_opacity: 0.6 - property int web_view_overlay_vertical_margin: web_view_corner_radius + property int web_view_overlay_vertical_padding: web_view_corner_radius property int web_view_overley_border_width: 1 property int window_default_height: 780 diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml index a8f5812d..99e8d357 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml @@ -27,15 +27,8 @@ Item { function showBlankPage() { webView.loadHtml("blank", ""); } - - function showUnderConstruction() { - webView.loadHtml(` - - -Coming soon -

The content of this page is under construction.

- `, "") - root.visible = true; + function loadHTML(html) { + webView.loadHtml(html) } Rectangle { @@ -46,16 +39,19 @@ Item { } Rectangle { anchors.fill: parent - anchors.margins: overlay ? ProtonStyle.web_view_overlay_margin : 0 + anchors.bottomMargin: overlay ? ProtonStyle.web_view_overlay_vertical_margin : 0 + anchors.leftMargin: overlay ? ProtonStyle.web_view_overlay_horizontal_margin : 0 + anchors.rightMargin: overlay ? ProtonStyle.web_view_overlay_horizontal_margin : 0 + anchors.topMargin: overlay ? ProtonStyle.web_view_overlay_vertical_margin : 0 color: root.colorScheme.background_norm radius: ProtonStyle.web_view_corner_radius ColumnLayout { anchors.bottomMargin: 0 anchors.fill: parent - anchors.leftMargin: overlay ? ProtonStyle.web_view_overlay_horizontal_margin : 0 - anchors.rightMargin: overlay ? ProtonStyle.web_view_overlay_horizontal_margin : 0 - anchors.topMargin: overlay ? ProtonStyle.web_view_overlay_vertical_margin : 0 + anchors.leftMargin: overlay ? ProtonStyle.web_view_overlay_horizontal_padding : 0 + anchors.rightMargin: overlay ? ProtonStyle.web_view_overlay_horizontal_padding : 0 + anchors.topMargin: overlay ? ProtonStyle.web_view_overlay_vertical_padding : 0 spacing: 0 Rectangle { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/Template.html b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/Template.html new file mode 100644 index 00000000..b9fad96e --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/Template.html @@ -0,0 +1,16 @@ + + + + + + + + +%1 + + \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyBridge.html b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyBridge.html new file mode 100644 index 00000000..a26f4486 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyBridge.html @@ -0,0 +1,19 @@ +

Why do I need bridge?

+

+ Proton does not have access to the content of your messages, so it cannot share your unencrypted messages with your email client from the + Proton servers. +

+

+ Email clients such as Microsoft Outlook, Mozilla Thunderbird and Apple Mail use standard protocols named IMAP and SMTP to receive and send emails. +

+

+ Even though the IMAP and SMTP protocols can use secure channels (using SSL/TLS), they do not offer support for encrypted messages. + Because Proton does not have access to the content of your messages, it is not possible to configure your email client to connect directly to + Proton servers. +

+

+ The key to solving this problem is Bridge. Once installed on your computer and connected to your Proton account, Bridge can access your + encrypted messages stored on the Proton servers. Bridge integrates an IMAP and a SMTP server that run on your computer and are accessible only + to applications executing on your machine. Your email client connects to these local servers and Bridge is responsible for seamlessly encrypting + and decrypting the messages that you send and receive. +

diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyCertificate.html b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyCertificate.html new file mode 100644 index 00000000..045ab10b --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyCertificate.html @@ -0,0 +1,19 @@ +

Why do I need to install a certificate when configuring Apple Mail with Bridge?

+

+ Apple Mail requires a secure channel for communications with email servers, and the server needs to be acknowledged as trusted. +

+

+ In order to communicate with Bridge, Apple Mail requires secure connections using SSL/TLS. This cryptographic protocol includes an identity + verification system using certificates. For publicly available servers, certificates are normally issued and digitally signed by a certificate + authority, such as Let's Encrypt. This is not possible for Bridge, as the IMAP and SMTP servers are running on your own computer, and are not + accessible from any network (local or internet). +

+

+ The solution is to use a self-signed certificate. When setting up an email account where the server provides a self-signed certificate, most + email clients will issue a warning asking you whether you trust the server or not, because the certificate was not issued by a certificate + authority. +

+

+ Apple Mail requires an extra step. It will simply refuse to connect if the certificate is not set as trusted. Bridge solves this by storing this + certificate in the macOS keychain. This operation requires that you provide your macOS account password. +

diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyProfileWarning.html b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyProfileWarning.html new file mode 100644 index 00000000..3b426460 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyProfileWarning.html @@ -0,0 +1,21 @@ +

Why is there a warning sign when installing the Bridge profile on macOS?

+

+ This warning indicates that the certificate used to secure the communication channel between Bridge and your email client is not signed by a + trusted third party. +

+

+ In order to communicate with Bridge, Apple Mail requires secure connections using SSL/TLS. This cryptographic protocol includes an identity + verification system using certificates. For publicly available servers, certificates are normally issued and digitally signed by a certificate + authority, such as Let's Encrypt. This is not possible for Bridge, as the IMAP and SMTP servers are running on your own computer, and are not + accessible from any network (local or internet). +

+

+ The solution is to use a self-signed certificate. When setting up an email account where the server provides a self-signed certificate, most + email clients will issue a warning asking you whether you trust the server or not, because the certificate was not issued by a certificate + authority. The client has no way of verifying that the server is who it pretends to be. +

+

+ You can safely ignore this warning. The check concerns only the communication between your email client and Bridge, which occurs within your + computer. On the other end, the communication between Bridge and the Proton servers uses the HTTPS protocol, and the identity of the remote + server is verified by Bridge. +

diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml index d7516a3b..b537c44e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml @@ -28,7 +28,6 @@ Item { signal appleMailAutoconfigProfileInstallPageShow function showAutoconfig() { - certificateInstall.waitingForCert = false; if (Backend.isTLSCertificateInstalled()) { showProfileInstall(); } else { @@ -41,6 +40,7 @@ Item { appleMailAutoconfigCertificateInstallPageShown(); } function showProfileInstall() { + profileInstall.reset(); stack.currentIndex = ClientConfigAppleMail.Screen.ProfileInstall; appleMailAutoconfigProfileInstallPageShow(); } @@ -193,6 +193,13 @@ Item { // stack index 1 Item { id: profileInstall + + property bool profilePaneLaunched: false + + function reset() { + profilePaneLaunched = false; + } + Layout.fillHeight: true Layout.fillWidth: true @@ -239,11 +246,15 @@ Item { Button { Layout.fillWidth: true colorScheme: wizard.colorScheme - text: qsTr("Install the profile") + text: profileInstall.profilePaneLaunched ? qsTr("I have installed the profile") : qsTr("Install the profile") onClicked: { - wizard.user.configureAppleMail(wizard.address); - wizard.showClientConfigEnd(); + if (profileInstall.profilePaneLaunched) { + wizard.showClientConfigEnd(); + } else { + wizard.user.configureAppleMail(wizard.address); + profileInstall.profilePaneLaunched = true; + } } } Button { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 88cf9e27..0def652c 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -26,7 +26,7 @@ Item { function showAppleMailAutoconfigCertificateInstall() { showAppleMailAutoconfigCommon(); descriptionLabel.text = qsTr("Apple Mail configuration is mostly automated, but in order to work, Bridge needs to install a certificate in your keychain."); - linkLabel1.setCallback(showUnderConstruction, qsTr("Why is this certificate needed?"), false); + linkLabel1.setCallback(function() { Backend.showHelpOverlay("WhyCertificate.html"); }, qsTr("Why is this certificate needed?"), false); } function showAppleMailAutoconfigCommon() { titleLabel.text = ""; @@ -39,7 +39,7 @@ Item { function showAppleMailAutoconfigProfileInstall() { showAppleMailAutoconfigCommon(); descriptionLabel.text = qsTr("The final step before you can start using Apple Mail is to install the Bridge server profile in the system preferences.\n\nAdding a server profile is necessary to ensure that your Mac can receive and send Proton Mails."); - linkLabel1.setCallback(showUnderConstruction, qsTr("Why is there a yellow warning sign?"), false); + linkLabel1.setCallback(function() { Backend.showHelpOverlay("WhyProfileWarning.html"); }, qsTr("Why is there a yellow warning sign?"), false); linkLabel2.setCallback(wizard.showClientParams, qsTr("Configure Apple Mail manually"), false); } function showClientSelector(newAccount = true) { @@ -63,15 +63,12 @@ Item { function showOnboarding() { titleLabel.text = (Backend.users.count === 0) ? qsTr("Welcome to\nProton Mail Bridge") : qsTr("Add a Proton Mail account"); descriptionLabel.text = qsTr("Bridge is the gateway between your Proton account and your email client. It runs in the background and encrypts and decrypts your messages seamlessly. "); - linkLabel1.setCallback(showUnderConstruction, qsTr("Why do I need Bridge?"), false); + linkLabel1.setCallback(function() { Backend.showHelpOverlay("WhyBridge.html"); }, qsTr("Why do I need Bridge?"), false); linkLabel2.clear(); root.iconSource = "/qml/icons/img-welcome.svg"; root.iconHeight = 148; root.iconWidth = 265; } - function showUnderConstruction() { - wizard.showUnderConstruction(); - } Connections { function onLogin2FARequested() { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 060d3b93..3248184a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -43,7 +43,6 @@ Item { signal bugReportRequested signal wizardEnded - signal showUnderConstruction function _showClientConfig() { showClientConfig(root.user, root.address, false); From c8cf90abfed14006070000dc71c9a8febf29ca1b Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Fri, 1 Sep 2023 11:03:48 +0200 Subject: [PATCH 62/93] feat(GODT-2772): use os browser instead of integrated one for external links (for now). --- .../bridge-gui/bridge-gui/QMLBackend.h | 2 -- .../bridge-gui/bridge-gui/Resources.qrc | 1 - .../bridge-gui/bridge-gui/qml/Bridge.qml | 29 ++------------- .../bridge-gui/bridge-gui/qml/HelpView.qml | 2 +- .../bridge-gui/bridge-gui/qml/MainWindow.qml | 7 +--- .../bridge-gui/qml/Proton/WebFrame.qml | 3 -- .../SetupWizard/ClientConfigParameters.qml | 2 +- .../bridge-gui/qml/WebFrameWindow.qml | 35 ------------------- 8 files changed, 6 insertions(+), 75 deletions(-) delete mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/WebFrameWindow.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h index 035fbca8..70692b70 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h @@ -278,8 +278,6 @@ signals: // Signals received from the Go backend, to be forwarded to QML void hideMainWindow(); ///< Signal for the 'hideMainWindow' gRPC stream event. void showHelp(); ///< Signal for the 'showHelp' event (from the context menu). void showSettings(); ///< Signal for the 'showHelp' event (from the context menu). - void showWebFrameWindow(QString const &url); ///< Signal the the 'showWebFrameWindow' event - void showWebFrameOverlay(QString const &url); ////< Signal for the 'showWebFrameOverlay' event. void showWebFrameOverlayHTML(QString const &html); ///< Signal to display HTML content in a web frame overlay. void selectUser(QString const& userID, bool forceShowWindow); ///< Signal emitted in order to selected a user with a given ID in the list. void genericError(QString const &title, QString const &description); ///< Signal for the 'genericError' gRPC stream event. diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index dccfa1ca..3c27df20 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -130,6 +130,5 @@ qml/ConnectionModeSettings.qml qml/SplashScreen.qml qml/Status.qml - qml/WebFrameWindow.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml index 96a9e5d7..09d18726 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml @@ -40,33 +40,10 @@ QtObject { function onHideMainWindow() { mainWindow.hide(); } - - target: Backend - } - WebFrameWindow { - id: webFrameWindow - colorScheme: ProtonStyle.currentStyle - flags: Qt.Tool - transientParent: mainWindow - - Connections { - function onShowWebFrameOverlay(url) { - mainWindow.showWebFrameOverlay(url); - } - function onShowWebFrameOverlayHTML(html) { - mainWindow.showWebFrameOverlayHTML(html) - } - function onShowWebFrameWindow(url) { - webFrameWindow.url = url; - webFrameWindow.show(); - webFrameWindow.raise(); - if (!webFrameWindow.active) { - webFrameWindow.requestActivate(); - } - } - - target: Backend + function onShowWebFrameOverlayHTML(html) { + mainWindow.showWebFrameOverlayHTML(html) } + target: Backend } } property Notifications _notifications: Notifications { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml b/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml index b27efd52..e12516f6 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml @@ -104,7 +104,7 @@ SettingsView { type: Label.Caption onLinkActivated: function (link) { - Backend.showWebFrameOverlay(link) + Qt.openUrlExternally(link) } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index 575981f2..03498eca 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -55,7 +55,7 @@ ApplicationWindow { setupWizard.showClientConfig(user, address, justLoggedIn); } function showHelp() { - Backend.showWebFrameWindow("https://proton.me/support/bridge"); + Qt.openUrlExternally("https://proton.me/support/bridge"); } function showLocalCacheSettings() { contentWrapper.showLocalCacheSettings(); @@ -67,11 +67,6 @@ ApplicationWindow { function showSettings() { contentWrapper.showSettings(); } - function showWebFrameOverlay(url) { - webFrameOverlay.visible = true; - webFrameOverlay.url = url; - } - function showWebFrameOverlayHTML(html) { webFrameOverlay.loadHTML(html); webFrameOverlay.visible = true; diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml index 99e8d357..9bfe5eba 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml @@ -24,9 +24,6 @@ Item { property bool overlay: true property string url: "" - function showBlankPage() { - webView.loadHtml("blank", ""); - } function loadHTML(html) { webView.loadHtml(html) } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml index 709b692c..eed7ff89 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml @@ -79,7 +79,7 @@ Rectangle { text: qsTr("Open guide") onClicked: function () { - Backend.showWebFrameWindow(wizard.setupGuideLink()); + Qt.openUrlExternally(wizard.setupGuideLink()); } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/WebFrameWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/WebFrameWindow.qml deleted file mode 100644 index 597bcea6..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/WebFrameWindow.qml +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2023 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 . -import QtQml -import QtQuick -import QtQuick.Controls -import Proton - -Window { - id: root - - property string url - property ColorScheme colorScheme - - height: 600 - minimumWidth: 600 - width: 800 - - WebFrame { - id: frame - anchors.fill: parent - colorScheme: root.colorScheme - overlay: false - url: root.url - } -} From 139ad7539414a72b508137856af14fc32273f94f Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Tue, 12 Sep 2023 10:50:15 +0200 Subject: [PATCH 63/93] feat(GODT-2772): removed web frame. --- .../bridge-gui/bridge-gui/CMakeLists.txt | 3 +- .../bridge-gui/bridge-gui/QMLBackend.cpp | 22 ----- .../bridge-gui/bridge-gui/QMLBackend.h | 2 - .../bridge-gui/bridge-gui/Resources.qrc | 1 - .../frontend/bridge-gui/bridge-gui/main.cpp | 2 - .../bridge-gui/bridge-gui/qml/Bridge.qml | 3 - .../bridge-gui/bridge-gui/qml/MainWindow.qml | 12 --- .../bridge-gui/qml/Proton/WebFrame.qml | 83 ------------------- .../bridge-gui/qml/SetupWizard/LeftPane.qml | 7 +- 9 files changed, 5 insertions(+), 130 deletions(-) delete mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/CMakeLists.txt b/internal/frontend/bridge-gui/bridge-gui/CMakeLists.txt index 84bca59b..2c14ed83 100644 --- a/internal/frontend/bridge-gui/bridge-gui/CMakeLists.txt +++ b/internal/frontend/bridge-gui/bridge-gui/CMakeLists.txt @@ -75,7 +75,7 @@ if(NOT UNIX) set(CMAKE_INSTALL_BINDIR ".") endif(NOT UNIX) -find_package(Qt6 COMPONENTS Core Quick Qml QuickControls2 Widgets Svg WebView REQUIRED) +find_package(Qt6 COMPONENTS Core Quick Qml QuickControls2 Widgets Svg REQUIRED) qt_standard_project_setup() set(CMAKE_AUTORCC ON) message(STATUS "Using Qt ${Qt6_VERSION}") @@ -148,7 +148,6 @@ target_link_libraries(bridge-gui Qt6::Qml Qt6::QuickControls2 Qt6::Svg - Qt6::WebView sentry::sentry bridgepp ) diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp index 8631a823..3bdd3012 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp @@ -1099,28 +1099,6 @@ void QMLBackend::setUpdateTrayIcon(QString const &stateString, QString const &st } -//**************************************************************************************************************************************************** -/// \param[in] helpFileName The name of the help file with extension (e.g. "WhyBridge.html"). -//**************************************************************************************************************************************************** -void QMLBackend::showHelpOverlay(QString const &helpFileName) { - QDir const basePath(":/qml/Resources/Help"); - QString const templatePath = basePath.filePath("Template.html"); - QFile templateFile(templatePath); - if (!templateFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - app().log().error("Could not load help overlay HTML template"); - return; - } - - QFile helpFile(basePath.filePath(helpFileName)); - if (!helpFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - app().log().error(QString("Could not load help overlay HTML file %1").arg(helpFileName)); - return; - } - - emit showWebFrameOverlayHTML(QString::fromUtf8(templateFile.readAll()).arg(QString::fromUtf8(helpFile.readAll()))); -} - - //**************************************************************************************************************************************************** /// \param[in] isOn Does bridge consider internet as on. //**************************************************************************************************************************************************** diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h index 70692b70..f19f3113 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h @@ -211,7 +211,6 @@ public slots: // slots for functions that need to be processed locally. void setErrorTrayIcon(QString const& stateString, QString const &statusIcon); ///< Set the tray icon to 'error' state. void setWarnTrayIcon(QString const& stateString, QString const &statusIcon); ///< Set the tray icon to 'warn' state. void setUpdateTrayIcon(QString const& stateString, QString const &statusIcon); ///< Set the tray icon to 'update' state. - void showHelpOverlay(QString const &helpFileName); ///< Slot triggering the display of help content as an overlay. public slots: // slot for signals received from gRPC that need transformation instead of simple forwarding void internetStatusChanged(bool isOn); ///< Check if bridge considers internet as on. @@ -278,7 +277,6 @@ signals: // Signals received from the Go backend, to be forwarded to QML void hideMainWindow(); ///< Signal for the 'hideMainWindow' gRPC stream event. void showHelp(); ///< Signal for the 'showHelp' event (from the context menu). void showSettings(); ///< Signal for the 'showHelp' event (from the context menu). - void showWebFrameOverlayHTML(QString const &html); ///< Signal to display HTML content in a web frame overlay. void selectUser(QString const& userID, bool forceShowWindow); ///< Signal emitted in order to selected a user with a given ID in the list. void genericError(QString const &title, QString const &description); ///< Signal for the 'genericError' gRPC stream event. void imapLoginWhileSignedOut(QString const& username); ///< Signal for the notification of IMAP login attempt on a signed out account. diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index 3c27df20..19126b21 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -107,7 +107,6 @@ qml/Proton/TextArea.qml qml/Proton/TextField.qml qml/Proton/Toggle.qml - qml/Proton/WebFrame.qml qml/QuestionItem.qml qml/Resources/bug_report_flow.json qml/Resources/Help/Template.html diff --git a/internal/frontend/bridge-gui/bridge-gui/main.cpp b/internal/frontend/bridge-gui/bridge-gui/main.cpp index 6da1e70e..dc8eca14 100644 --- a/internal/frontend/bridge-gui/bridge-gui/main.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/main.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #ifdef Q_OS_MACOS @@ -285,7 +284,6 @@ int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL); // must be called before instantiating the BridgeApp } - QtWebView::initialize(); BridgeApp guiApp(argc, argv); initSentry(); auto sentryCloser = qScopeGuard([] { sentry_close(); }); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml index 09d18726..7bd9f4b6 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml @@ -40,9 +40,6 @@ QtObject { function onHideMainWindow() { mainWindow.hide(); } - function onShowWebFrameOverlayHTML(html) { - mainWindow.showWebFrameOverlayHTML(html) - } target: Backend } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index 03498eca..7d012e84 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -67,10 +67,6 @@ ApplicationWindow { function showSettings() { contentWrapper.showSettings(); } - function showWebFrameOverlayHTML(html) { - webFrameOverlay.loadHTML(html); - webFrameOverlay.visible = true; - } colorScheme: ProtonStyle.currentStyle height: ProtonStyle.window_default_height @@ -178,14 +174,6 @@ ApplicationWindow { } } } - WebFrame { - id: webFrameOverlay - anchors.fill: parent - colorScheme: root.colorScheme - overlay: true - url: "" - visible: false - } NotificationPopups { colorScheme: root.colorScheme mainWindow: root diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml deleted file mode 100644 index 9bfe5eba..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/WebFrame.qml +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) 2023 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 . -import QtQml -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import QtQuick.Controls.impl -import QtWebView - -Item { - id: root - - property ColorScheme colorScheme - property bool overlay: true - property string url: "" - - function loadHTML(html) { - webView.loadHtml(html) - } - - Rectangle { - anchors.fill: parent - color: "#000" - opacity: ProtonStyle.web_view_overlay_opacity - visible: overlay - } - Rectangle { - anchors.fill: parent - anchors.bottomMargin: overlay ? ProtonStyle.web_view_overlay_vertical_margin : 0 - anchors.leftMargin: overlay ? ProtonStyle.web_view_overlay_horizontal_margin : 0 - anchors.rightMargin: overlay ? ProtonStyle.web_view_overlay_horizontal_margin : 0 - anchors.topMargin: overlay ? ProtonStyle.web_view_overlay_vertical_margin : 0 - color: root.colorScheme.background_norm - radius: ProtonStyle.web_view_corner_radius - - ColumnLayout { - anchors.bottomMargin: 0 - anchors.fill: parent - anchors.leftMargin: overlay ? ProtonStyle.web_view_overlay_horizontal_padding : 0 - anchors.rightMargin: overlay ? ProtonStyle.web_view_overlay_horizontal_padding : 0 - anchors.topMargin: overlay ? ProtonStyle.web_view_overlay_vertical_padding : 0 - spacing: 0 - - Rectangle { - Layout.fillHeight: true - Layout.fillWidth: true - border.color: root.colorScheme.border_norm - border.width: overlay ? ProtonStyle.web_view_overley_border_width : 0 - - WebView { - id: webView - anchors.fill: parent - anchors.margins: ProtonStyle.web_view_overley_border_width - url: root.url - } - } - Button { - Layout.alignment: Qt.AlignCenter - Layout.bottomMargin: ProtonStyle.web_view_overlay_button_vertical_margin - Layout.preferredWidth: ProtonStyle.web_view_button_width - Layout.topMargin: ProtonStyle.web_view_overlay_button_vertical_margin - colorScheme: root.colorScheme - text: qsTr("Close") - visible: overlay - - onClicked: { - root.url = ""; - root.visible = false; - } - } - } - } -} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 0def652c..054f66f6 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -26,7 +26,8 @@ Item { function showAppleMailAutoconfigCertificateInstall() { showAppleMailAutoconfigCommon(); descriptionLabel.text = qsTr("Apple Mail configuration is mostly automated, but in order to work, Bridge needs to install a certificate in your keychain."); - linkLabel1.setCallback(function() { Backend.showHelpOverlay("WhyCertificate.html"); }, qsTr("Why is this certificate needed?"), false); + linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why is this certificate needed?"), true); ///< TODO GODT-2772: replace link with link to KB article. + linkLabel2.clear(); } function showAppleMailAutoconfigCommon() { titleLabel.text = ""; @@ -39,7 +40,7 @@ Item { function showAppleMailAutoconfigProfileInstall() { showAppleMailAutoconfigCommon(); descriptionLabel.text = qsTr("The final step before you can start using Apple Mail is to install the Bridge server profile in the system preferences.\n\nAdding a server profile is necessary to ensure that your Mac can receive and send Proton Mails."); - linkLabel1.setCallback(function() { Backend.showHelpOverlay("WhyProfileWarning.html"); }, qsTr("Why is there a yellow warning sign?"), false); + linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why is there a yellow warning sign?"), true); ///< TODO GODT-2772: replace link with link to KB article. linkLabel2.setCallback(wizard.showClientParams, qsTr("Configure Apple Mail manually"), false); } function showClientSelector(newAccount = true) { @@ -63,7 +64,7 @@ Item { function showOnboarding() { titleLabel.text = (Backend.users.count === 0) ? qsTr("Welcome to\nProton Mail Bridge") : qsTr("Add a Proton Mail account"); descriptionLabel.text = qsTr("Bridge is the gateway between your Proton account and your email client. It runs in the background and encrypts and decrypts your messages seamlessly. "); - linkLabel1.setCallback(function() { Backend.showHelpOverlay("WhyBridge.html"); }, qsTr("Why do I need Bridge?"), false); + linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why do I need Bridge?"), true); ///< TODO GODT-2772: replace link with link to KB article. linkLabel2.clear(); root.iconSource = "/qml/icons/img-welcome.svg"; root.iconHeight = 148; From 09d87023f1827bb8caccd7c1d9ea9d959c822b78 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Tue, 12 Sep 2023 10:52:00 +0200 Subject: [PATCH 64/93] feat(GODT-2772): removed web engine from deploy. This partly reverts commit c89d206a9576499c3df29139c8df9099a053a839. --- Makefile | 3 +++ .../bridge-gui/bridge-gui/DeployDarwin.cmake | 4 ---- .../bridge-gui/bridge-gui/DeployLinux.cmake | 16 +--------------- .../bridge-gui/bridge-gui/DeployWindows.cmake | 2 -- 4 files changed, 4 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index b4653fc7..6f010be6 100644 --- a/Makefile +++ b/Makefile @@ -139,6 +139,9 @@ ${DEPLOY_DIR}/darwin: ${EXE_TARGET} build-launcher perl -i -pe"s/>${BRIDGE_GUI_EXE_NAME}/>${LAUNCHER_EXE}/g" ${DARWINAPP_CONTENTS}/Info.plist cp ./dist/${SRC_ICNS} ${DARWINAPP_CONTENTS}/Resources/${SRC_ICNS} cp LICENSE ${DARWINAPP_CONTENTS}/Resources/ + rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebEngine.framework" + rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebView.framework" + rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebEngineCore.framework" mv ${LAUNCHER_EXE} ${DARWINAPP_CONTENTS}/MacOS/${LAUNCHER_EXE} ./utils/remove_non_relative_links_darwin.sh "${EXE_TARGET_DARWIN}/${EXE_BINARY_DARWIN}" diff --git a/internal/frontend/bridge-gui/bridge-gui/DeployDarwin.cmake b/internal/frontend/bridge-gui/bridge-gui/DeployDarwin.cmake index 03fc4b97..ce32255b 100644 --- a/internal/frontend/bridge-gui/bridge-gui/DeployDarwin.cmake +++ b/internal/frontend/bridge-gui/bridge-gui/DeployDarwin.cmake @@ -30,8 +30,6 @@ install(DIRECTORY "${QT_DIR}/qml/QtQml" DESTINATION "${CMAKE_INSTALL_PREFIX}/bridge-gui.app/Contents/MacOS") install(DIRECTORY "${QT_DIR}/qml/QtQuick" DESTINATION "${CMAKE_INSTALL_PREFIX}/bridge-gui.app/Contents/MacOS") -install(DIRECTORY "${QT_DIR}/qml/QtWebView" - DESTINATION "${CMAKE_INSTALL_PREFIX}/bridge-gui.app/Contents/MacOS") # FRAMEWORKS install(DIRECTORY "${QT_DIR}/lib/QtQmlWorkerScript.framework" DESTINATION "${CMAKE_INSTALL_PREFIX}/bridge-gui.app/Contents/Frameworks") @@ -45,8 +43,6 @@ install(DIRECTORY "${QT_DIR}/lib/QtQuickDialogs2QuickImpl.framework" DESTINATION "${CMAKE_INSTALL_PREFIX}/bridge-gui.app/Contents/Frameworks") install(DIRECTORY "${QT_DIR}/lib/QtQuickDialogs2Utils.framework" DESTINATION "${CMAKE_INSTALL_PREFIX}/bridge-gui.app/Contents/Frameworks") -install(DIRECTORY "${QT_DIR}/lib/QtWebViewQuick.framework" - DESTINATION "${CMAKE_INSTALL_PREFIX}/bridge-gui.app/Contents/Frameworks") # PLUGINS install(FILES "${QT_DIR}/plugins/imageformats/libqsvg.dylib" DESTINATION "${CMAKE_INSTALL_PREFIX}/bridge-gui.app/Contents/PlugIns/imageformats") diff --git a/internal/frontend/bridge-gui/bridge-gui/DeployLinux.cmake b/internal/frontend/bridge-gui/bridge-gui/DeployLinux.cmake index a97ccb87..10e1ea16 100644 --- a/internal/frontend/bridge-gui/bridge-gui/DeployLinux.cmake +++ b/internal/frontend/bridge-gui/bridge-gui/DeployLinux.cmake @@ -22,11 +22,7 @@ cmake_minimum_required(VERSION 3.22) #***************************************************************************************************************************************************** set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_BINDIR}" "${CMAKE_INSTALL_LIBDIR}" "." "../lib") -install(DIRECTORY - "${QT_DIR}/qml" - "${QT_DIR}/plugins" - "${QT_DIR}/translations" - "${QT_DIR}/resources" +install(DIRECTORY "${QT_DIR}/qml" "${QT_DIR}/plugins" DESTINATION "${CMAKE_INSTALL_PREFIX}") macro( AppendLib LIB_NAME HINT_PATH) @@ -72,13 +68,6 @@ AppendQt6Lib("libQt6PrintSupport.so.6") AppendQt6Lib("libQt6Xml.so.6") AppendQt6Lib("libQt6OpenGLWidgets.so.6") AppendQt6Lib("libQt6QuickWidgets.so.6") -AppendQt6Lib("libQt6Positioning.so.6") -AppendQt6Lib("libQt6WebChannel.so.6") -AppendQt6Lib("libQt6WebView.so.6") -AppendQt6Lib("libQt6WebViewQuick.so.6") -AppendQt6Lib("libQt6WebEngineCore.so.6") -AppendQt6Lib("libQt6WebEngineQuick.so.6") -AppendQt6Lib("libQt6WebEngineQuickDelegatesQml.so.6") # QML dependencies AppendQt6Lib("libQt6QmlWorkerScript.so.6") @@ -92,6 +81,3 @@ AppendQt6Lib("libQt6Svg.so.6") AppendQt6Lib("libQt6QmlCore.so.6") install(FILES ${DEPLOY_LIBS} DESTINATION "${CMAKE_INSTALL_PREFIX}/lib") - -# Install QtWebEngineProcess to be able to use WebView -install(PROGRAMS "${QT_DIR}/libexec/QtWebEngineProcess" DESTINATION "${CMAKE_INSTALL_PREFIX}/libexec") diff --git a/internal/frontend/bridge-gui/bridge-gui/DeployWindows.cmake b/internal/frontend/bridge-gui/bridge-gui/DeployWindows.cmake index 99b0037c..85573307 100644 --- a/internal/frontend/bridge-gui/bridge-gui/DeployWindows.cmake +++ b/internal/frontend/bridge-gui/bridge-gui/DeployWindows.cmake @@ -71,8 +71,6 @@ install(FILES ${DEPLOY_LIBS} DESTINATION "${CMAKE_INSTALL_PREFIX}") install(DIRECTORY ${QT_DIR}/qml/Qt/labs/platform DESTINATION "${CMAKE_INSTALL_PREFIX}/Qt/labs/") install(DIRECTORY ${QT_DIR}/qml/QtQml DESTINATION "${CMAKE_INSTALL_PREFIX}") install(DIRECTORY ${QT_DIR}/qml/QtQuick DESTINATION "${CMAKE_INSTALL_PREFIX}") -install(DIRECTORY ${QT_DIR}/qml/QtWebView DESTINATION "${CMAKE_INSTALL_PREFIX}") -install(DIRECTORY ${QT_DIR}/qml/QtWebEngine DESTINATION "${CMAKE_INSTALL_PREFIX}") # crash handler utils install(PROGRAMS "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/tools/sentry-native/crashpad_handler.exe" DESTINATION "${CMAKE_INSTALL_PREFIX}") From 0ab0f2f4ffb61fd28db52036a497f6d5f3cd186c Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Fri, 15 Sep 2023 09:54:45 +0200 Subject: [PATCH 65/93] feat(GODT-2772): setup wizard report knowledge base article opening event. --- .../bridge-gui-tester/GRPCService.cpp | 26 +++++++++++++++++++ .../bridge-gui-tester/GRPCService.h | 3 +++ .../bridge-gui/bridge-gui/QMLBackend.cpp | 15 ++++++++++- .../bridge-gui/bridge-gui/QMLBackend.h | 3 ++- .../bridge-gui/bridge-gui/qml/HelpView.qml | 3 +-- .../bridge-gui/bridge-gui/qml/MainWindow.qml | 2 +- .../qml/Notifications/Notifications.qml | 5 +--- .../SetupWizard/ClientConfigParameters.qml | 2 +- .../bridge-gui/qml/SetupWizard/HelpButton.qml | 3 +-- .../bridge-gui/qml/SetupWizard/LeftPane.qml | 6 ++--- 10 files changed, 53 insertions(+), 15 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp index 89a15e4f..ac171993 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp @@ -802,6 +802,32 @@ Status GRPCService::InstallTLSCertificate(ServerContext *, Empty const *, Empty } +//**************************************************************************************************************************************************** +/// \param[in] request The request. +//**************************************************************************************************************************************************** +Status GRPCService::KBArticleClicked(::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 diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h index 224108c5..fe7ec6ab 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h @@ -96,6 +96,9 @@ 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 KBArticleClicked(::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. diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp index 3bdd3012..d7b60760 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp @@ -40,7 +40,8 @@ using namespace bridgepp; namespace { - QString const bugReportFile = ":qml/Resources/bug_report_flow.json"; +QString const bugReportFile = ":qml/Resources/bug_report_flow.json"; +QString const bridgeKBUrl = "https://proton.me/support/bridge"; ///< The URL for the root of the bridge knowledge base. } @@ -290,6 +291,18 @@ bool QMLBackend::isTLSCertificateInstalled() { } +//**************************************************************************************************************************************************** +/// \param[in] url The URL of the knowledge base article. If empty/invalid, the home page for the Bridge knowledge base is opened. +//**************************************************************************************************************************************************** +void QMLBackend::openKBArticle(QString const &url) { + HANDLE_EXCEPTION( + QString const u = url.isEmpty() ? bridgeKBUrl : url; + QDesktopServices::openUrl(u); + emit notifyKBArticleClicked(u); + ) +} + + //**************************************************************************************************************************************************** /// \return The value for the 'showOnStartup' property. //**************************************************************************************************************************************************** diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h index f19f3113..775d3e7e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h @@ -65,6 +65,7 @@ public: // member functions. Q_INVOKABLE QString collectAnswers(quint8 categoryId) const; ///< Collect answer for a given set of questions. Q_INVOKABLE void clearAnswers(); ///< Clear all collected answers. Q_INVOKABLE bool isTLSCertificateInstalled(); ///< Check if the bridge certificate is installed in the OS keychain. + Q_INVOKABLE void openKBArticle(QString const & url = QString()); ///< Open a knowledge base article. public: // Qt/QML properties. Note that the NOTIFY-er signal is required even for read-only properties (QML warning otherwise) Q_PROPERTY(bool showOnStartup READ showOnStartup NOTIFY showOnStartupChanged) @@ -276,7 +277,7 @@ signals: // Signals received from the Go backend, to be forwarded to QML void showMainWindow(); ///< Signal for the 'showMainWindow' gRPC stream event. void hideMainWindow(); ///< Signal for the 'hideMainWindow' gRPC stream event. void showHelp(); ///< Signal for the 'showHelp' event (from the context menu). - void showSettings(); ///< Signal for the 'showHelp' event (from the context menu). + void showSettings(); ///< Signal for the 'showSettings' event (from the context menu). void selectUser(QString const& userID, bool forceShowWindow); ///< Signal emitted in order to selected a user with a given ID in the list. void genericError(QString const &title, QString const &description); ///< Signal for the 'genericError' gRPC stream event. void imapLoginWhileSignedOut(QString const& username); ///< Signal for the notification of IMAP login attempt on a signed out account. diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml b/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml index e12516f6..3f2199b1 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml @@ -36,8 +36,7 @@ SettingsView { type: SettingsItem.PrimaryButton onClicked: { - Backend.notifyKBArticleClicked("https://proton.me/support/bridge"); - Backend.showHelp() + Backend.openKBArticle(); } } SettingsItem { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index 7d012e84..dcb9b5b7 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -55,7 +55,7 @@ ApplicationWindow { setupWizard.showClientConfig(user, address, justLoggedIn); } function showHelp() { - Qt.openUrlExternally("https://proton.me/support/bridge"); + contentWrapper.showHelp(); } function showLocalCacheSettings() { contentWrapper.showLocalCacheSettings(); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notifications.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notifications.qml index 04b2398d..4aacc22a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notifications.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notifications.qml @@ -788,8 +788,6 @@ QtObject { } } property Notification rebuildKeychain: Notification { - property var supportLink: "https://proton.me/support/bridge" - brief: title description: qsTr("Bridge is not able to access your macOS keychain. Please consult the instructions on our support page.") group: Notifications.Group.Dialogs | Notifications.Group.Configuration @@ -802,8 +800,7 @@ QtObject { text: qsTr("Open the support page") onTriggered: { - Backend.notifyKBArticleClicked(root.rebuildKeychain.supportLink); - Qt.openUrlExternally(root.rebuildKeychain.supportLink); + Backend.openKBArticle(); Backend.quit(); } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml index eed7ff89..924c4f1a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml @@ -79,7 +79,7 @@ Rectangle { text: qsTr("Open guide") onClicked: function () { - Qt.openUrlExternally(wizard.setupGuideLink()); + Backend.openKBArticle(wizard.setupGuideLink()); } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml index 7dcf4e80..8040793b 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml @@ -49,8 +49,7 @@ Button { text: qsTr("Get help") onClicked: { - Backend.notifyKBArticleClicked("https://proton.me/support/bridge"); - Backend.showHelp(); + Backend.openKBArticle(); } } MenuItem { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 054f66f6..20c7e0ff 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -26,7 +26,7 @@ Item { function showAppleMailAutoconfigCertificateInstall() { showAppleMailAutoconfigCommon(); descriptionLabel.text = qsTr("Apple Mail configuration is mostly automated, but in order to work, Bridge needs to install a certificate in your keychain."); - linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why is this certificate needed?"), true); ///< TODO GODT-2772: replace link with link to KB article. + linkLabel1.setCallback(function() { Backend.openKBArticle(); }, qsTr("Why is this certificate needed?"), true); ///< TODO GODT-2772: replace link with link to KB article. linkLabel2.clear(); } function showAppleMailAutoconfigCommon() { @@ -40,7 +40,7 @@ Item { function showAppleMailAutoconfigProfileInstall() { showAppleMailAutoconfigCommon(); descriptionLabel.text = qsTr("The final step before you can start using Apple Mail is to install the Bridge server profile in the system preferences.\n\nAdding a server profile is necessary to ensure that your Mac can receive and send Proton Mails."); - linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why is there a yellow warning sign?"), true); ///< TODO GODT-2772: replace link with link to KB article. + linkLabel1.setCallback(function() { Backend.openKBArticle(); }, qsTr("Why is there a yellow warning sign?"), true); ///< TODO GODT-2772: replace link with link to KB article. linkLabel2.setCallback(wizard.showClientParams, qsTr("Configure Apple Mail manually"), false); } function showClientSelector(newAccount = true) { @@ -64,7 +64,7 @@ Item { function showOnboarding() { titleLabel.text = (Backend.users.count === 0) ? qsTr("Welcome to\nProton Mail Bridge") : qsTr("Add a Proton Mail account"); descriptionLabel.text = qsTr("Bridge is the gateway between your Proton account and your email client. It runs in the background and encrypts and decrypts your messages seamlessly. "); - linkLabel1.setLink("https://proton.me/support/bridge", qsTr("Why do I need Bridge?"), true); ///< TODO GODT-2772: replace link with link to KB article. + linkLabel1.setCallback(function() { Backend.openKBArticle(); }, qsTr("Why do I need Bridge?"), true); ///< TODO GODT-2772: replace link with link to KB article. linkLabel2.clear(); root.iconSource = "/qml/icons/img-welcome.svg"; root.iconHeight = 148; From ab1281ceeee404421bdd741c3fdfb6f41a403770 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Fri, 15 Sep 2023 16:35:11 +0200 Subject: [PATCH 66/93] feat(GODT-2772): added final link to knowledge base articles. --- .../bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 20c7e0ff..c63cec46 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -26,7 +26,7 @@ Item { function showAppleMailAutoconfigCertificateInstall() { showAppleMailAutoconfigCommon(); descriptionLabel.text = qsTr("Apple Mail configuration is mostly automated, but in order to work, Bridge needs to install a certificate in your keychain."); - linkLabel1.setCallback(function() { Backend.openKBArticle(); }, qsTr("Why is this certificate needed?"), true); ///< TODO GODT-2772: replace link with link to KB article. + linkLabel1.setCallback(function() { Backend.openKBArticle("https://proton.me/support/bridge/apple-mail-certificate"); }, qsTr("Why is this certificate needed?"), true); linkLabel2.clear(); } function showAppleMailAutoconfigCommon() { @@ -40,7 +40,7 @@ Item { function showAppleMailAutoconfigProfileInstall() { showAppleMailAutoconfigCommon(); descriptionLabel.text = qsTr("The final step before you can start using Apple Mail is to install the Bridge server profile in the system preferences.\n\nAdding a server profile is necessary to ensure that your Mac can receive and send Proton Mails."); - linkLabel1.setCallback(function() { Backend.openKBArticle(); }, qsTr("Why is there a yellow warning sign?"), true); ///< TODO GODT-2772: replace link with link to KB article. + linkLabel1.setCallback(function() { Backend.openKBArticle("https://proton.me/support/bridge/macos-certificate-warning"); }, qsTr("Why is there a yellow warning sign?"), true); linkLabel2.setCallback(wizard.showClientParams, qsTr("Configure Apple Mail manually"), false); } function showClientSelector(newAccount = true) { @@ -64,7 +64,7 @@ Item { function showOnboarding() { titleLabel.text = (Backend.users.count === 0) ? qsTr("Welcome to\nProton Mail Bridge") : qsTr("Add a Proton Mail account"); descriptionLabel.text = qsTr("Bridge is the gateway between your Proton account and your email client. It runs in the background and encrypts and decrypts your messages seamlessly. "); - linkLabel1.setCallback(function() { Backend.openKBArticle(); }, qsTr("Why do I need Bridge?"), true); ///< TODO GODT-2772: replace link with link to KB article. + linkLabel1.setCallback(function() { Backend.openKBArticle("https://proton.me/support/bridge/why-you-need-bridge"); }, qsTr("Why do I need Bridge?"), true); linkLabel2.clear(); root.iconSource = "/qml/icons/img-welcome.svg"; root.iconHeight = 148; From f8b86a76dd70a77c364337318b248d9c788665b5 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Tue, 19 Sep 2023 07:57:11 +0200 Subject: [PATCH 67/93] feat(GODT-2772): fixed missing space in error message. --- .../bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml index b537c44e..b2487d7a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml @@ -78,7 +78,7 @@ Item { Connections { function onCertificateInstallCanceled() { certificateInstall.waitingForCert = false; - certificateInstall.errorString = qsTr("Apple Mail cannot be configured if you do not install the certificate.Please retry."); + certificateInstall.errorString = qsTr("Apple Mail cannot be configured if you do not install the certificate. Please retry."); certificateInstall.showBugReportLink = false; } function onCertificateInstallFailed() { From a1a5ffba5d37bf91761b0bb502dade4805489d04 Mon Sep 17 00:00:00 2001 From: Jakub Date: Mon, 25 Sep 2023 12:00:10 +0200 Subject: [PATCH 68/93] chore: Vasco da Gama Bridge 3.6.0 changelog. --- Changelog.md | 20 ++++++++++++++++++++ Makefile | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 918165f2..dc1149a4 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,6 +3,26 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) +## Vasco da Gama Bridge 3.6.0 + +### Added +* GODT-2762: Setup wizard. +* GODT-2772: Setup wizard content. +* GODT-2769: Setup Wizard architecture. +* GODT-2767: Setup Wizard foundations. + +### Changed +* GODT-2771: Cert related tools for macOS. +* GODT-2770: Proof of concept for web view as a tool window and overlay (not used). +* GODT-2916: Split Decryption from Message Building. +* GODT-2597: Implement contact specific settings in integration tests. +* GODT-2664: Trigger QA installer. + +### Fixed +* Fixed missing GoOs gRPC call in bridge-gui-tester. +* GODT-2929: Message dedup with different text transfer encoding. + + ## Umshiang Bridge 3.5.0 ### Added diff --git a/Makefile b/Makefile index 6f010be6..462ce114 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) .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.5.0+git +BRIDGE_APP_VERSION?=3.6.0+git APP_VERSION:=${BRIDGE_APP_VERSION} APP_FULL_NAME:=Proton Mail Bridge APP_VENDOR:=Proton AG From e422b28bc39bf0cb728c67e81960f5b91b5ab91e Mon Sep 17 00:00:00 2001 From: Leander Beernaert Date: Mon, 25 Sep 2023 14:32:46 +0200 Subject: [PATCH 69/93] fix(GODT-2212): Preserver Header order in message building https://github.com/ProtonMail/go-proton-api/pull/100 --- COPYING_NOTES.md | 1 + go.mod | 3 ++- go.sum | 6 +++-- .../syncservice/stage_download_test.go | 16 ++++++------- pkg/message/build.go | 4 ++-- pkg/message/build_framework_test.go | 23 ++++++++++++++----- pkg/message/build_test.go | 6 +++-- 7 files changed, 37 insertions(+), 22 deletions(-) diff --git a/COPYING_NOTES.md b/COPYING_NOTES.md index 1fc10d94..8fbda386 100644 --- a/COPYING_NOTES.md +++ b/COPYING_NOTES.md @@ -123,6 +123,7 @@ 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) * [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) diff --git a/go.mod b/go.mod index d5882959..a1a498b0 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Masterminds/semver/v3 v3.2.0 github.com/ProtonMail/gluon v0.17.1-0.20230911134257-5eb2eeebbef5 github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a - github.com/ProtonMail/go-proton-api v0.4.1-0.20230915070741-3de73982c764 + github.com/ProtonMail/go-proton-api v0.4.1-0.20230925123025-331ad8e6d5ee github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton github.com/PuerkitoBio/goquery v1.8.1 github.com/abiosoft/ishell v2.0.0+incompatible @@ -108,6 +108,7 @@ require ( github.com/ugorji/go/codec v1.2.11 // indirect 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 golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.9.0 // indirect golang.org/x/mod v0.8.0 // indirect diff --git a/go.sum b/go.sum index 034e0724..4c9c029b 100644 --- a/go.sum +++ b/go.sum @@ -35,8 +35,8 @@ github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7 h1:+j+Kd/ github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= -github.com/ProtonMail/go-proton-api v0.4.1-0.20230915070741-3de73982c764 h1:2rEmoo5BgEap+9Y484xAL8cod1bbjDaeWaGFLS/a1Ec= -github.com/ProtonMail/go-proton-api v0.4.1-0.20230915070741-3de73982c764/go.mod h1:nS8hMGjJLgC0Iej0JMYbsI388LesEkM1Hj/jCCxQeaQ= +github.com/ProtonMail/go-proton-api v0.4.1-0.20230925123025-331ad8e6d5ee h1:CzFXOiflEZZqT3HQqj2I5AkIprRbc/c6/lToPdEKzxM= +github.com/ProtonMail/go-proton-api v0.4.1-0.20230925123025-331ad8e6d5ee/go.mod h1:Y3ea3i1UbqHz5vq43odmAAd6lmR4nx0ZIQ32tqMfxTY= github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI= github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk= github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton h1:YS6M20yvjCJPR1r4ADW5TPn6rahs4iAyZaACei86bEc= @@ -399,6 +399,8 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRT github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a h1:DxppxFKRqJ8WD6oJ3+ZXKDY0iMONQDl5UTg2aTyHh8k= +gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a/go.mod h1:NREvu3a57BaK0R1+ztrEzHWiZAihohNLQ6trPxlIqZI= 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= diff --git a/internal/services/syncservice/stage_download_test.go b/internal/services/syncservice/stage_download_test.go index ed640444..29f91638 100644 --- a/internal/services/syncservice/stage_download_test.go +++ b/internal/services/syncservice/stage_download_test.go @@ -340,10 +340,9 @@ func TestDownloadStage_JobAbortsOnAttachmentDownloadError(t *testing.T) { MessageMetadata: proton.MessageMetadata{ ID: "msg", }, - Header: "", - ParsedHeaders: nil, - Body: "", - MIMEType: "", + Header: "", + Body: "", + MIMEType: "", Attachments: []proton.Attachment{{ ID: "attach", }}, @@ -387,11 +386,10 @@ func buildDownloadStageData(tj *tjob, numMessages int, with422 bool) ([]string, ID: msgID, Size: len([]byte(msgID)), }, - Header: "", - ParsedHeaders: nil, - Body: msgID, - MIMEType: "", - Attachments: nil, + Header: "", + Body: msgID, + MIMEType: "", + Attachments: nil, }, AttData: nil, } diff --git a/pkg/message/build.go b/pkg/message/build.go index e79f287c..87fd73c2 100644 --- a/pkg/message/build.go +++ b/pkg/message/build.go @@ -503,8 +503,8 @@ func getAttachmentPartHeader(att proton.Attachment) message.Header { func toMessageHeader(hdr proton.Headers) message.Header { var res message.Header - for key, val := range hdr { - for _, val := range val { + for _, key := range hdr.Order { + for _, val := range hdr.Values[key] { // Using AddRaw instead of Add to save key-value pair as byte buffer within Header. // This buffer is used latter on in message writer to construct message and avoid crash // when key length is more than 76 characters long. diff --git a/pkg/message/build_framework_test.go b/pkg/message/build_framework_test.go index 516e224e..9f232945 100644 --- a/pkg/message/build_framework_test.go +++ b/pkg/message/build_framework_test.go @@ -64,12 +64,20 @@ func newTestMessageWithHeaders( func newRawTestMessageWithHeaders(messageID, addressID, mimeType, body string, date time.Time, headers map[string][]string) proton.Message { msgHeaders := proton.Headers{ - "Content-Type": {mimeType}, - "Date": {date.In(time.UTC).Format(time.RFC1123Z)}, + Values: map[string][]string{ + "Content-Type": {mimeType}, + "Date": {date.In(time.UTC).Format(time.RFC1123Z)}, + }, + Order: []string{"Content-Type", "Date"}, } for k, v := range headers { - msgHeaders[k] = v + _, ok := msgHeaders.Values[k] + if !ok { + msgHeaders.Order = append(msgHeaders.Order, k) + } + + msgHeaders.Values[k] = v } return proton.Message{ @@ -98,9 +106,12 @@ func addTestAttachment( Name: name, MIMEType: rfc822.MIMEType(mimeType), Headers: proton.Headers{ - "Content-Type": {mimeType}, - "Content-Disposition": {disposition}, - "Content-Transfer-Encoding": {"base64"}, + Values: map[string][]string{ + "Content-Type": {mimeType}, + "Content-Disposition": {disposition}, + "Content-Transfer-Encoding": {"base64"}, + }, + Order: []string{"Content-Type", "Content-Disposition", "Content-Transfer-Encoding"}, }, Disposition: proton.Disposition(disposition), KeyPackets: base64.StdEncoding.EncodeToString(enc.GetBinaryKeyPacket()), diff --git a/pkg/message/build_test.go b/pkg/message/build_test.go index 63807012..580d495c 100644 --- a/pkg/message/build_test.go +++ b/pkg/message/build_test.go @@ -54,7 +54,8 @@ func TestBuildPlainMessageWithLongKey(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "text/plain", "body", time.Now()) - msg.ParsedHeaders["ReallyVeryVeryVeryVeryVeryLongLongLongLongLongLongLongKeyThatWillHaveNotSoLongValue"] = []string{"value"} + msg.ParsedHeaders.Values["ReallyVeryVeryVeryVeryVeryLongLongLongLongLongLongLongKeyThatWillHaveNotSoLongValue"] = []string{"value"} + msg.ParsedHeaders.Order = append(msg.ParsedHeaders.Order, "ReallyVeryVeryVeryVeryVeryLongLongLongLongLongLongLongKeyThatWillHaveNotSoLongValue") res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) @@ -977,7 +978,8 @@ func TestBuildIncludeMessageIDReference(t *testing.T) { msg := newTestMessage(t, kr, "messageID", "addressID", "text/plain", "body", time.Now()) // Add references. - msg.ParsedHeaders["References"] = []string{""} + msg.ParsedHeaders.Values["References"] = []string{""} + msg.ParsedHeaders.Order = append(msg.ParsedHeaders.Order, "References") res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) From cf3abaa96f029a2efb3482c2f614cb6437f0f20d Mon Sep 17 00:00:00 2001 From: Leander Beernaert Date: Tue, 26 Sep 2023 09:08:25 +0200 Subject: [PATCH 70/93] fix(GODT-2949): Fix close of close channel in event service This issue is triggered due to the `Service.Close()` call after the go-routine for the event service exists. It is possible that during this period a recently added subscriber with `pendingOpAdd` gets cancelled and closed. However, the subscriber later also enqueues a `pendingOpRemove` which gets processed again with a call in `user.eventService.Close()` leading to the double close panic. This patch simply removes the `s.Close()` from the service, and leaves the cleanup to called externally from user.Close() or user.Logout(). --- internal/services/userevents/service.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/services/userevents/service.go b/internal/services/userevents/service.go index 52e6d83d..104dfbe8 100644 --- a/internal/services/userevents/service.go +++ b/internal/services/userevents/service.go @@ -192,7 +192,6 @@ func (s *Service) run(ctx context.Context, lastEventID string) { defer s.cpc.Close() defer s.timer.Stop() defer s.log.Info("Exiting service") - defer s.Close() client := network.NewClientRetryWrapper(s.eventSource, &network.ExpCoolDown{}) @@ -303,14 +302,15 @@ func (s *Service) Close() { // Cleanup pending removes. for _, s := range s.pendingSubscriptions { - if s.op == pendingOpRemove { - if !processed.Contains(s.sub) { + if !processed.Contains(s.sub) { + processed.Add(s.sub) + + if s.op == pendingOpRemove { + s.sub.close() + } else { + s.sub.cancel() s.sub.close() } - } else { - s.sub.cancel() - s.sub.close() - processed.Add(s.sub) } } From c0992e8801172a51ed151ea6aecac4b704717dfc Mon Sep 17 00:00:00 2001 From: Leander Beernaert Date: Tue, 26 Sep 2023 09:20:01 +0200 Subject: [PATCH 71/93] fix(GODT-2590): Fix send on closed channel Ensure periodic user tasks are terminated before the other user services. The panic triggered due to the fact that the telemetry service was shutdown before this periodic task. --- internal/user/user.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/user/user.go b/internal/user/user.go index 4e1470a6..e916bc81 100644 --- a/internal/user/user.go +++ b/internal/user/user.go @@ -589,6 +589,8 @@ func (user *User) Logout(ctx context.Context, withAPI bool) error { return fmt.Errorf("failed to remove user from imap server: %w", err) } + user.tasks.CancelAndWait() + // Stop Services user.serviceGroup.CancelAndWait() @@ -598,8 +600,6 @@ func (user *User) Logout(ctx context.Context, withAPI bool) error { // Close imap service. user.imapService.Close() - user.tasks.CancelAndWait() - if withAPI { user.log.Debug("Logging out from API") @@ -621,6 +621,9 @@ func (user *User) Logout(ctx context.Context, withAPI bool) error { func (user *User) Close() { user.log.Info("Closing user") + // Stop any ongoing background tasks. + user.tasks.CancelAndWait() + // Stop Services user.serviceGroup.CancelAndWait() @@ -630,9 +633,6 @@ func (user *User) Close() { // Close imap service. user.imapService.Close() - // Stop any ongoing background tasks. - user.tasks.CancelAndWait() - // Close the user's API client. user.client.Close() From 76f2e7fdb990844cd4e86ca68f8478c938eee378 Mon Sep 17 00:00:00 2001 From: Leander Beernaert Date: Tue, 26 Sep 2023 09:45:27 +0200 Subject: [PATCH 72/93] fix(GODT-2951): Negative WaitGroup Counter Do not defer call to `wg.Done()` in `job.onJobFinished`. If there is an error it will also call `wg.Done()`. --- internal/services/syncservice/job.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/services/syncservice/job.go b/internal/services/syncservice/job.go index 8978ae6b..cad55c90 100644 --- a/internal/services/syncservice/job.go +++ b/internal/services/syncservice/job.go @@ -113,13 +113,14 @@ func (j *Job) onStageCompleted(ctx context.Context, count int64) { } func (j *Job) onJobFinished(ctx context.Context, lastMessageID string, count int64) { - defer j.wg.Done() - if err := j.state.SetLastMessageID(ctx, lastMessageID, count); err != nil { j.log.WithError(err).Error("Failed to store last synced message id") j.onError(err) return } + + // j.onError() also calls j.wg.Done(). + j.wg.Done() j.syncReporter.OnProgress(ctx, count) } From f4958b9b53be2d126ba067d584d09cd13011fd6e Mon Sep 17 00:00:00 2001 From: Leander Beernaert Date: Tue, 26 Sep 2023 12:47:53 +0200 Subject: [PATCH 73/93] fix(GODT-2956): Restore old deletion rules When unlabeling a message from trash we have to check if this message is present in another folder before perma-deleting. --- internal/services/imapservice/connector.go | 66 ++++++++++++++++++- tests/features/imap/message/copy.feature | 15 +++++ .../imap/message/delete_from_trash.feature | 6 +- 3 files changed, 82 insertions(+), 5 deletions(-) diff --git a/internal/services/imapservice/connector.go b/internal/services/imapservice/connector.go index 475bbfa6..04ff2ab9 100644 --- a/internal/services/imapservice/connector.go +++ b/internal/services/imapservice/connector.go @@ -37,6 +37,7 @@ import ( "github.com/ProtonMail/proton-bridge/v3/pkg/message" "github.com/ProtonMail/proton-bridge/v3/pkg/message/parser" "github.com/bradenaw/juniper/stream" + "github.com/bradenaw/juniper/xslices" "github.com/sirupsen/logrus" "golang.org/x/exp/slices" ) @@ -334,8 +335,69 @@ func (s *Connector) RemoveMessagesFromMailbox(ctx context.Context, _ connector.I } if mboxID == proton.TrashLabel || mboxID == proton.DraftsLabel { - if err := s.client.DeleteMessage(ctx, msgIDs...); err != nil { - return err + const ChunkSize = 150 + var msgToPermaDelete []string + + rdLabels := s.labels.Read() + defer rdLabels.Close() + + // There's currently no limit on how many IDs we can filter on, + // but to be nice to API, let's chunk it by 150. + for _, messageIDs := range xslices.Chunk(messageIDs, ChunkSize) { + metadata, err := s.client.GetMessageMetadataPage(ctx, 0, ChunkSize, proton.MessageFilter{ + ID: usertypes.MapTo[imap.MessageID, string](messageIDs), + }) + if err != nil { + return err + } + + // If a message is not preset in any other label other than AllMail, AllDrafts and AllSent, it can be + // permanently deleted. + for _, m := range metadata { + var remainingLabels []string + + for _, id := range m.LabelIDs { + label, ok := rdLabels.GetLabel(id) + if !ok { + // Handle case where this label was newly introduced and we do not yet know about it. + logrus.WithField("labelID", id).Warnf("Unknown label found during expung from Trash, attempting to locate it") + label, err = s.client.GetLabel(ctx, id, proton.LabelTypeFolder, proton.LabelTypeSystem, proton.LabelTypeSystem) + if err != nil { + if errors.Is(err, proton.ErrNoSuchLabel) { + logrus.WithField("labelID", id).Warn("Label does not exist, ignoring") + continue + } + + logrus.WithField("labelID", id).Errorf("Failed to resolve label: %v", err) + return fmt.Errorf("failed to resolve label: %w", err) + } + } + if !WantLabel(label) { + continue + } + + if label.Type == proton.LabelTypeSystem && (id == proton.AllDraftsLabel || + id == proton.AllMailLabel || + id == proton.AllSentLabel || + id == proton.AllScheduledLabel) { + continue + } + + remainingLabels = append(remainingLabels, m.ID) + } + + if len(remainingLabels) == 0 { + msgToPermaDelete = append(msgToPermaDelete, m.ID) + } + } + } + + if len(msgToPermaDelete) != 0 { + logrus.Debugf("Following message(s) will be perma-deleted: %v", msgToPermaDelete) + + if err := s.client.DeleteMessage(ctx, msgToPermaDelete...); err != nil { + return err + } } } diff --git a/tests/features/imap/message/copy.feature b/tests/features/imap/message/copy.feature index 4dd0b6c6..1b44d794 100644 --- a/tests/features/imap/message/copy.feature +++ b/tests/features/imap/message/copy.feature @@ -85,3 +85,18 @@ Feature: IMAP copy messages | from | to | subject | unread | | john.doe@mail.com | [user:user]@[domain] | foo | false | + Scenario: Move message to trash then copy to folder does not delete message + When IMAP client "1" moves the message with subject "foo" from "INBOX" to "Trash" + And it succeeds + Then IMAP client "1" eventually sees the following messages in "Trash": + | from | to | subject | unread | + | john.doe@mail.com | [user:user]@[domain] | foo | false | + When IMAP client "1" copies the message with subject "foo" from "Trash" to "Folders/mbox" + And it succeeds + When IMAP client "1" marks the message with subject "foo" as deleted + Then it succeeds + When IMAP client "1" expunges + Then it succeeds + Then IMAP client "1" eventually sees the following messages in "Folders/mbox": + | from | to | subject | unread | + | john.doe@mail.com | [user:user]@[domain] | foo | false | diff --git a/tests/features/imap/message/delete_from_trash.feature b/tests/features/imap/message/delete_from_trash.feature index a8323e91..15446eed 100644 --- a/tests/features/imap/message/delete_from_trash.feature +++ b/tests/features/imap/message/delete_from_trash.feature @@ -7,7 +7,7 @@ Feature: IMAP remove messages from Trash | label | label | Then it succeeds - Scenario Outline: Message in Trash and some other label is permanently deleted + Scenario Outline: Message in Trash and some other label is not permanently deleted Given the address "[user:user]@[domain]" of account "[user:user]" has the following messages in "Trash": | from | to | subject | body | | john.doe@mail.com | [user:user]@[domain] | foo | hello | @@ -27,8 +27,8 @@ Feature: IMAP remove messages from Trash When IMAP client "1" expunges Then it succeeds And IMAP client "1" eventually sees 1 messages in "Trash" - And IMAP client "1" eventually sees 1 messages in "All Mail" - And IMAP client "1" eventually sees 0 messages in "Labels/label" + And IMAP client "1" eventually sees 2 messages in "All Mail" + And IMAP client "1" eventually sees 1 messages in "Labels/label" Scenario Outline: Message in Trash only is permanently deleted Given the address "[user:user]@[domain]" of account "[user:user]" has the following messages in "Trash": From 07c03c69200dd0ab5240e98e02eb7bef07e094ef Mon Sep 17 00:00:00 2001 From: Leander Beernaert Date: Wed, 27 Sep 2023 11:30:46 +0200 Subject: [PATCH 74/93] fix(GODT-2963): Use multi error to report file removal errors Do not abort removing files on first error. Collect errors and try to remove as many as possible. This would cause some state files to not be removed on windows. --- pkg/files/removal.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/files/removal.go b/pkg/files/removal.go index 8f0b8db3..594f3010 100644 --- a/pkg/files/removal.go +++ b/pkg/files/removal.go @@ -72,11 +72,12 @@ func remove(dir string, except ...string) error { sort.Sort(sort.Reverse(sort.StringSlice(toRemove))) + var multiErr error for _, target := range toRemove { if err := os.RemoveAll(target); err != nil { - return err + multiErr = multierror.Append(multiErr, err) } } - return nil + return multiErr } From e9c73c2d0d4b573c22b778cfe13a0e6b4bd7121e Mon Sep 17 00:00:00 2001 From: Jakub Date: Wed, 27 Sep 2023 15:34:50 +0200 Subject: [PATCH 75/93] chore: Umshiang Bridge 3.5.1 changelog. --- Changelog.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Changelog.md b/Changelog.md index dc1149a4..5257eb13 100644 --- a/Changelog.md +++ b/Changelog.md @@ -23,6 +23,17 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) * GODT-2929: Message dedup with different text transfer encoding. + +## Umshiang Bridge 3.5.1 + +### Fixed +* GODT-2963: Use multi error to report file removal errors. +* GODT-2956: Restore old deletion rules. +* GODT-2951: Negative WaitGroup Counter. +* GODT-2590: Fix send on closed channel. +* GODT-2949: Fix close of close channel in event service. + + ## Umshiang Bridge 3.5.0 ### Added From 50acc0dcfbdf99c3ec3bf5058afa3c6e514070f4 Mon Sep 17 00:00:00 2001 From: Romain Le Jeune Date: Wed, 27 Sep 2023 14:17:51 +0000 Subject: [PATCH 76/93] feat(GODT-2725): Implement receive message step with expected structure exposed. --- tests/features/imap/message/import.feature | 235 ++++++++++++++++++--- tests/imap_test.go | 21 ++ tests/steps_test.go | 1 + tests/types_test.go | 204 ++++++++++++++++++ tests/user_test.go | 2 +- 5 files changed, 433 insertions(+), 30 deletions(-) diff --git a/tests/features/imap/message/import.feature b/tests/features/imap/message/import.feature index f264287b..fb0c4046 100644 --- a/tests/features/imap/message/import.feature +++ b/tests/features/imap/message/import.feature @@ -21,9 +21,19 @@ Feature: IMAP import messages Hello """ Then it succeeds - And IMAP client "1" eventually sees the following messages in "INBOX": - | from | to | subject | body | - | bridgetest@pm.test | bridgetest@example.com | Basic text/plain message | Hello | + And IMAP client "1" eventually sees the following message in "INBOX" with this structure: + """ + { + "from": "Bridge Test ", + "date": "01 Jan 80 00:00 +0000", + "to": "Internal Bridge ", + "subject": "Basic text/plain message", + "content": { + "content-type": "text/plain", + "body-is": "Hello" + } + } + """ Scenario: Import message with double charset in content type When IMAP client "1" appends the following message to "INBOX": @@ -39,9 +49,22 @@ Feature: IMAP import messages Hello """ Then it succeeds - And IMAP client "1" eventually sees the following messages in "INBOX": - | from | to | subject | body | - | bridgetest@pm.test | bridgetest@example.com | Message with double charset in content type | Hello | + And IMAP client "1" eventually sees the following message in "INBOX" with this structure: + """ + { + "from": "Bridge Test ", + "date": "01 Jan 80 00:00 +0000", + "to": "Internal Bridge ", + "subject": "Message with double charset in content type", + "content": { + "content-type": "text/plain", + "content-type-charset": "utf-8", + "content-disposition": "", + "transfer-encoding": "quoted-printable", + "body-is": "Hello" + } + } + """ Scenario: Import message with attachment name encoded by RFC 2047 without quoting @@ -69,31 +92,87 @@ Feature: IMAP import messages """ Then it succeeds +# And IMAP client "1" eventually sees the following message in "INBOX" with this structure: +# """ +# { +# "from": "Bridge Test ", +# "date": "01 Jan 80 00:00 +0000", +# "to": "Internal Bridge ", +# "subject": "Message with attachment name encoded by RFC 2047 without quoting", +# "body-contains": "Hello", +# "content": { +# "content-type": "multipart/mixed; boundary=\"boundary\"", +# "sections":[ +# { +# "content-type": "text/plain", +# "body-is": "Hello" +# }, +# { +# "content-type": "application/pdf", +# "content-type-name": "=?US-ASCII?Q?filename?=", +# "content-disposition": "attachment", +# "content-disposition-filename": "=?US-ASCII?Q?filename?=", +# "body-is": "somebytes" +# } +# ] +# } +# } +# """ # The message is imported as UTF-8 and the content type is determined at build time. Scenario: Import message as latin1 without content type When IMAP client "1" appends "plain/text_plain_unknown_latin1.eml" to "INBOX" Then it succeeds - And IMAP client "1" eventually sees the following messages in "INBOX": - | from | to | body | - | sender@pm.me | receiver@pm.me | ééééééé | + And IMAP client "1" eventually sees the following message in "INBOX" with this structure: + """ + { + "from": "Sender ", + "date": "01 Jan 80 00:00 +0000", + "to": "Receiver ", + "content": { + "content-type": "text/plain", + "body-is": "ééééééé" + } + } + """ # The message is imported and the body is converted to UTF-8. Scenario: Import message as latin1 with content type When IMAP client "1" appends "plain/text_plain_latin1.eml" to "INBOX" Then it succeeds - And IMAP client "1" eventually sees the following messages in "INBOX": - | from | to | body | - | sender@pm.me | receiver@pm.me | ééééééé | + And IMAP client "1" eventually sees the following message in "INBOX" with this structure: + """ + { + "from": "Sender ", + "date": "01 Jan 80 00:00 +0000", + "to": "Receiver ", + "content": { + "content-type": "text/plain", + "content-type-charset": "utf-8", + "body-is": "ééééééé" + } + } + """ + # The message is imported anad the body is wrongly converted (body is corrupted). Scenario: Import message as latin1 with wrong content type When IMAP client "1" appends "plain/text_plain_wrong_latin1.eml" to "INBOX" Then it succeeds - And IMAP client "1" eventually sees the following messages in "INBOX": - | from | to | - | sender@pm.me | receiver@pm.me | + And IMAP client "1" eventually sees the following message in "INBOX" with this structure: + """ + { + "from": "Sender ", + "date": "01 Jan 80 00:00 +0000", + "to": "Receiver ", + "content": { + "content-type": "text/plain", + "content-type-charset": "utf-8", + "body-is": "" + } + } + """ Scenario: Import received message to Sent When IMAP client "1" appends the following message to "Sent": @@ -107,9 +186,19 @@ Feature: IMAP import messages Hello """ Then it succeeds - And IMAP client "1" eventually sees the following messages in "Sent": - | from | to | subject | body | - | foo@example.com | bridgetest@pm.test | Hello | Hello | + And IMAP client "1" eventually sees the following message in "Sent" with this structure: + """ + { + "from": "Foo ", + "date": "01 Jan 80 00:00 +0000", + "to": "Bridge Test ", + "subject": "Hello", + "content": { + "content-type": "text/plain", + "body-is": "Hello" + } + } + """ And IMAP client "1" eventually sees 0 messages in "Inbox" Scenario: Import non-received message to Inbox @@ -123,11 +212,22 @@ Feature: IMAP import messages Hello """ Then it succeeds - And IMAP client "1" eventually sees the following messages in "INBOX": - | from | to | subject | body | - | foo@example.com | bridgetest@pm.test | Hello | Hello | + And IMAP client "1" eventually sees the following message in "INBOX" with this structure: + """ + { + "from": "Foo ", + "date": "01 Jan 80 00:00 +0000", + "to": "Bridge Test ", + "subject": "Hello", + "content": { + "content-type": "text/plain", + "body-is": "Hello" + } + } + """ And IMAP client "1" eventually sees 0 messages in "Sent" + Scenario: Import non-received message to Sent When IMAP client "1" appends the following message to "Sent": """ @@ -139,10 +239,20 @@ Feature: IMAP import messages Hello """ Then it succeeds - And IMAP client "1" eventually sees the following messages in "Sent": - | from | to | subject | body | - | foo@example.com | bridgetest@pm.test | Hello | Hello | And IMAP client "1" eventually sees 0 messages in "Inbox" + And IMAP client "1" eventually sees the following message in "Sent" with this structure: + """ + { + "from": "Foo ", + "date": "01 Jan 80 00:00 +0000", + "to": "Bridge Test ", + "subject": "Hello", + "content": { + "content-type": "text/plain", + "body-is": "Hello" + } + } + """ Scenario Outline: Import message without sender to When IMAP client "1" appends the following message to "": @@ -155,16 +265,53 @@ Feature: IMAP import messages Nope. """ Then it succeeds - And IMAP client "1" eventually sees the following messages in "": - | to | subject | body | - | lionel@richie.com | RE: Hello, is it me you looking for? | Nope. | - + And IMAP client "1" eventually sees the following message in "" with this structure: + """ + { + "from": "Somebody@somewhere.org", + "date": "01 Jan 80 00:00 +0000", + "to": "Lionel Richie ", + "subject": "RE: Hello, is it me you looking for?", + "content": { + "content-type": "text/plain", + "content-type-charset":"utf-8", + "transfer-encoding":"quoted-printable", + "body-is": "Nope." + } + } + """ Examples: | mailbox | - | Drafts | | Archive | | Sent | + Scenario: Import message without sender to Drafts + When IMAP client "1" appends the following message to "Drafts": + """ + From: Somebody@somewhere.org + Date: 01 Jan 1980 00:00:00 +0000 + To: Lionel Richie + Subject: RE: Hello, is it me you looking for? + + Nope. + """ + Then it succeeds + And IMAP client "1" eventually sees the following message in "Drafts" with this structure: + """ + { + "date": "01 Jan 01 00:00 +0000", + "to": "Lionel Richie ", + "subject": "RE: Hello, is it me you looking for?", + "content": { + "content-type": "text/plain", + "content-type-charset":"utf-8", + "transfer-encoding":"quoted-printable", + "body-is": "Nope." + } + } + """ + + Scenario: Import embedded message When IMAP client "1" appends the following message to "INBOX": """ @@ -198,3 +345,33 @@ Feature: IMAP import messages """ Then it succeeds +# And IMAP client "1" eventually sees the following message in "INBOX" with this structure: +# """ +# { +# "from": "Foo ", +# "date": "01 Jan 80 00:00 +0000", +# "to": "Bridge Test ", +# "subject": "Embedded message", +# "body-contains": "Hello", +# "content": { +# "content-type": "multipart/mixed", +# "body-contains": "This is a multi-part message in MIME format.", +# "sections":[ +# { +# "content-type": "text/plain", +# "content-type-charset": "utf-8", +# "transfer-encoding": "7bit", +# "body-is": "" +# }, +# { +# "content-type": "message/rfc822", +# "content-type-name": "embedded.eml", +# "transfer-encoding": "7bit", +# "content-disposition": "attachment", +# "content-disposition-filename": "embedded.eml", +# "body-is": "From: Bar \n\rTo: Bridge Test \n\rSubject: (No Subject)\n\rContent-Type: text/plain; charset=utf-8\n\rContent-Transfer-Encoding: quoted-printable\n\r\n\rhello" +# } +# ] +# } +# } +# """ diff --git a/tests/imap_test.go b/tests/imap_test.go index b11e7813..4165984f 100644 --- a/tests/imap_test.go +++ b/tests/imap_test.go @@ -19,6 +19,7 @@ package tests import ( "bytes" + "encoding/json" "fmt" "io" "os" @@ -342,6 +343,26 @@ func (s *scenario) imapClientEventuallySeesTheFollowingMessagesInMailbox(clientI }) } +func (s *scenario) imapClientSeesMessageInMailboxWithStructure(clientID, mailbox string, message *godog.DocString) error { + return eventually(func() error { + _, client := s.t.getIMAPClient(clientID) + + var msgStruct MessageStruct + if err := json.Unmarshal([]byte(message.Content), &msgStruct); err != nil { + return err + } + + fetch, err := clientFetch(client, mailbox) + if err != nil { + return err + } + + haveMessages := xslices.Map(fetch, newMessageStructFromIMAP) + + return matchStructure(haveMessages, msgStruct) + }) +} + func (s *scenario) imapClientSeesMessagesInMailbox(clientID string, count int, mailbox string) error { _, client := s.t.getIMAPClient(clientID) diff --git a/tests/steps_test.go b/tests/steps_test.go index f987c70a..5df03455 100644 --- a/tests/steps_test.go +++ b/tests/steps_test.go @@ -143,6 +143,7 @@ func (s *scenario) steps(ctx *godog.ScenarioContext) { ctx.Step(`^IMAP client "([^"]*)" moves the message with subject "([^"]*)" from "([^"]*)" to "([^"]*)"$`, s.imapClientMovesTheMessageWithSubjectFromTo) ctx.Step(`^IMAP client "([^"]*)" moves all messages from "([^"]*)" to "([^"]*)"$`, s.imapClientMovesAllMessagesFromTo) ctx.Step(`^IMAP client "([^"]*)" eventually sees the following messages in "([^"]*)":$`, s.imapClientEventuallySeesTheFollowingMessagesInMailbox) + ctx.Step(`^IMAP client "([^"]*)" eventually sees the following message in "([^"]*)" with this structure:$`, s.imapClientSeesMessageInMailboxWithStructure) ctx.Step(`^IMAP client "([^"]*)" eventually sees (\d+) messages in "([^"]*)"$`, s.imapClientEventuallySeesMessagesInMailbox) ctx.Step(`^IMAP client "([^"]*)" marks message (\d+) as deleted$`, s.imapClientMarksMessageAsDeleted) ctx.Step(`^IMAP client "([^"]*)" marks the message with subject "([^"]*)" as deleted$`, s.imapClientMarksTheMessageWithSubjectAsDeleted) diff --git a/tests/types_test.go b/tests/types_test.go index 27b7e982..f7d4526d 100644 --- a/tests/types_test.go +++ b/tests/types_test.go @@ -56,6 +56,31 @@ type Message struct { References string `bdd:"references"` } +type MessageStruct struct { + From string `json:"from"` + To string `json:"to"` + CC string `json:"cc"` + BCC string `json:"bcc"` + Subject string `json:"subject"` + Date string `json:"date"` + + Content MessageSection `json:"content"` +} + +type MessageSection struct { + ContentType string `json:"content-type"` + ContentTypeBoundary string `json:"content-type-boundary"` + ContentTypeCharset string `json:"content-type-charset"` + ContentTypeName string `json:"content-type-name"` + ContentDisposition string `json:"content-disposition"` + ContentDispositionFilename string `json:"content-disposition-filename"` + Sections []MessageSection `json:"sections"` + + TransferEncoding string `json:"transfer-encoding"` + BodyContains string `json:"body-contains"` + BodyIs string `json:"body-is"` +} + func (msg Message) Build() []byte { var b []byte @@ -166,6 +191,116 @@ func newMessageFromIMAP(msg *imap.Message) Message { return message } +func newMessageStructFromIMAP(msg *imap.Message) MessageStruct { + section, err := imap.ParseBodySectionName("BODY[]") + if err != nil { + panic(err) + } + + literal, err := io.ReadAll(msg.GetBody(section)) + if err != nil { + panic(err) + } + + m, err := message.Parse(bytes.NewReader(literal)) + if err != nil { + panic(err) + } + var body string + if m.MIMEType == rfc822.TextPlain { + body = strings.TrimSpace(string(m.PlainBody)) + } else { + body = strings.TrimSpace(string(m.RichBody)) + } + + message := MessageStruct{ + Subject: msg.Envelope.Subject, + Date: msg.Envelope.Date.Format(time.RFC822Z), + From: formatAddressList(msg.Envelope.From), + To: formatAddressList(msg.Envelope.To), + CC: formatAddressList(msg.Envelope.Cc), + BCC: formatAddressList(msg.Envelope.Bcc), + + Content: parseMessageSection(literal, body), + } + return message +} + +func formatAddressList(list []*imap.Address) string { + var res string + for idx, address := range list { + if address.PersonalName != "" { + res += address.PersonalName + " <" + address.Address() + ">" + } else { + res += address.Address() + } + if idx < len(list)-1 { + res += "; " + } + } + return res +} + +func parseMessageSection(literal []byte, body string) MessageSection { + mimeType, boundary, charset, name := parseContentType(literal) + + headers, err := rfc822.Parse(literal).ParseHeader() + if err != nil { + panic(err) + } + + msgSect := MessageSection{ + ContentType: string(mimeType), + ContentTypeBoundary: boundary, + ContentTypeCharset: charset, + ContentTypeName: name, + TransferEncoding: headers.Get("content-transfer-encoding"), + BodyIs: body, + } + + contentDisposition := bytes.Split([]byte(headers.Get("content-disposition")), []byte(";")) + for id, value := range contentDisposition { + if id == 0 { + msgSect.ContentDisposition = strings.TrimSpace(string(value)) + } + param := bytes.Split(value, []byte("=")) + if strings.TrimSpace(string(param[0])) == "filename" && len(param) >= 2 { + filename := strings.TrimPrefix(string(value), "filename=") + msgSect.ContentDispositionFilename = strings.TrimSpace(filename) + } + } + + if msgSect.ContentTypeBoundary != "" { + sections := bytes.Split([]byte(msgSect.BodyIs), []byte("--"+msgSect.ContentTypeBoundary)) + // Remove last element that will be the -- from finale boundary + sections = sections[:len(sections)-1] + for _, v := range sections { + msgSect.Sections = append(msgSect.Sections, parseMessageSection(v, string(v))) + } + } + return msgSect +} + +func parseContentType(literal []byte) (rfc822.MIMEType, string, string, string) { + mimeType, params, err := rfc822.Parse(literal).ContentType() + if err != nil { + panic(err) + } + boundary, ok := params["boundary"] + if !ok { + boundary = "" + } + charset, ok := params["charset"] + if !ok { + charset = "" + } + name, ok := params["name"] + if !ok { + name = "" + } + return mimeType, boundary, charset, name +} + func matchMessages(have, want []Message) error { slices.SortFunc(have, func(a, b Message) bool { return a.Subject < b.Subject @@ -182,6 +317,71 @@ func matchMessages(have, want []Message) error { return nil } +func matchStructure(have []MessageStruct, want MessageStruct) error { + for _, msg := range have { + if want.From != "" && msg.From != want.From { + continue + } + if want.To != "" && msg.To != want.To { + continue + } + if want.BCC != "" && msg.BCC != want.BCC { + continue + } + if want.CC != "" && msg.CC != want.CC { + continue + } + if want.Subject != "" && msg.Subject != want.Subject { + continue + } + if want.Date != "" && want.Date != msg.Date { + continue + } + + if matchContent(msg.Content, want.Content) { + return nil + } + } + return fmt.Errorf("missing messages: have %#v, want %#v", have, want) +} + +func matchContent(have MessageSection, want MessageSection) bool { + if want.ContentType != "" && want.ContentType != have.ContentType { + return false + } + if want.ContentTypeBoundary != "" && want.ContentTypeBoundary != have.ContentTypeBoundary { + return false + } + if want.ContentTypeCharset != "" && want.ContentTypeCharset != have.ContentTypeCharset { + return false + } + if want.ContentTypeName != "" && want.ContentTypeName != have.ContentTypeName { + return false + } + if want.ContentDisposition != "" && want.ContentDisposition != have.ContentDisposition { + return false + } + if want.ContentDispositionFilename != "" && want.ContentDispositionFilename != have.ContentDispositionFilename { + return false + } + if want.TransferEncoding != "" && want.TransferEncoding != have.TransferEncoding { + return false + } + if want.BodyContains != "" && strings.Contains(have.BodyIs, want.BodyContains) { + return false + } + if want.BodyIs != "" && want.BodyIs != have.BodyIs { + return false + } + for _, section := range want.Sections { + if !matchContent(have, section) { + return false + } + } + + return true +} + type Mailbox struct { Name string `bdd:"name"` Total int `bdd:"total"` @@ -336,3 +536,7 @@ type Contact struct { Sign string `bdd:"signature"` Encrypt string `bdd:"encryption"` } + +func FullAddress(addr *imap.Address) string { + return addr.PersonalName + " <" + addr.MailboxName + "@" + addr.HostName + ">" +} diff --git a/tests/user_test.go b/tests/user_test.go index afc446fa..7670cb3d 100644 --- a/tests/user_test.go +++ b/tests/user_test.go @@ -558,7 +558,7 @@ func (s *scenario) createUserAccount(username, password string, disabled bool) e if _, err := s.t.runQuarkCmd( context.Background(), "user:create:subscription", - "--planID", "plus", + "--planID", "visionary2022", string(userDecID), ); err != nil { return err From bb67d95669fce8f95c21df033dc1513267680873 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Wed, 27 Sep 2023 18:23:02 +0200 Subject: [PATCH 77/93] fix(GODT-2967): tray menu entries close the setup wizard when needed. --- .../frontend/bridge-gui/bridge-gui/qml/MainWindow.qml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index dcb9b5b7..a1e06bbd 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -106,18 +106,28 @@ ApplicationWindow { Connections { function onSelectUser(userID, forceShowWindow) { contentWrapper.selectUser(userID); + if (setupWizard.visible) { + setupWizard.closeWizard() + } if (forceShowWindow) { root.showAndRise(); } } function onShowHelp() { root.showHelp(); + if (setupWizard.visible) { + setupWizard.closeWizard() + } + root.showAndRise(); } function onShowMainWindow() { root.showAndRise(); } function onShowSettings() { + if (setupWizard.visible) { + setupWizard.closeWizard() + } root.showSettings(); root.showAndRise(); } From 56c53e918847aa3035e408f9472b4c7bebeb3781 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Thu, 28 Sep 2023 12:39:24 +0200 Subject: [PATCH 78/93] fix(GODT-2932): fix syncing not being reported in GUI. --- internal/frontend/bridge-gui/bridge-gui/UserList.cpp | 6 +++--- .../frontend/bridge-gui/bridgepp/bridgepp/User/User.cpp | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/UserList.cpp b/internal/frontend/bridge-gui/bridge-gui/UserList.cpp index 059d0b1e..b8a9946b 100644 --- a/internal/frontend/bridge-gui/bridge-gui/UserList.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/UserList.cpp @@ -262,7 +262,7 @@ void UserList::onUsedBytesChanged(QString const &userID, qint64 usedBytes) { void UserList::onSyncStarted(QString const &userID) { int const index = this->rowOfUserID(userID); if (index < 0) { - app().log().error(QString("Received onSyncStarted event for unknown userID %1").arg(userID)); + app().log().error(QString("Received syncStarted event for unknown userID %1").arg(userID)); return; } users_[index]->setIsSyncing(true); @@ -275,7 +275,7 @@ void UserList::onSyncStarted(QString const &userID) { void UserList::onSyncFinished(QString const &userID) { int const index = this->rowOfUserID(userID); if (index < 0) { - app().log().error(QString("Received onSyncFinished event for unknown userID %1").arg(userID)); + app().log().error(QString("Received syncFinished event for unknown userID %1").arg(userID)); return; } users_[index]->setIsSyncing(false); @@ -293,7 +293,7 @@ void UserList::onSyncProgress(QString const &userID, double progress, float elap Q_UNUSED(remainingMs) int const index = this->rowOfUserID(userID); if (index < 0) { - app().log().error(QString("Received onSyncFinished event for unknown userID %1").arg(userID)); + app().log().error(QString("Received syncProgress event for unknown userID %1").arg(userID)); return; } users_[index]->setSyncProgress(progress); diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/User/User.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/User/User.cpp index 0059f46a..85b0b7f4 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/User/User.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/User/User.cpp @@ -325,6 +325,10 @@ float User::syncProgress() const { /// \param[in] progress The progress ratio. //**************************************************************************************************************************************************** void User::setSyncProgress(float progress) { + // In some cases, we may have missed the syncStarted event because it was sent by bridge before the userChanged event, + // so we force the state to 'syncing' (GODT-2932). + this->setIsSyncing(true); + if (qAbs(syncProgress_ - progress) < 0.00001) { return; } From 8a6f96f9f2015258175ef8a1d185642f8fe4a84e Mon Sep 17 00:00:00 2001 From: Romain Le Jeune Date: Fri, 29 Sep 2023 07:08:10 +0000 Subject: [PATCH 79/93] fix(GODT-2965): fix multipart/mixed testdata + structure parsing steps related to this. --- tests/features/imap/message/import.feature | 78 +++++++++++++--------- tests/types_test.go | 41 +++++++----- 2 files changed, 74 insertions(+), 45 deletions(-) diff --git a/tests/features/imap/message/import.feature b/tests/features/imap/message/import.feature index fb0c4046..377cbaa7 100644 --- a/tests/features/imap/message/import.feature +++ b/tests/features/imap/message/import.feature @@ -322,11 +322,21 @@ Feature: IMAP import messages Content-Type: multipart/mixed; boundary="boundary" Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000 + --boundary + This is a multi-part message in MIME format. + --boundary Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 7bit + Hello + + --boundary + Content-Type: text/html; charset=utf-8 + Content-Transfer-Encoding: 7bit + +

HELLO

--boundary Content-Type: message/rfc822; name="embedded.eml" @@ -345,33 +355,41 @@ Feature: IMAP import messages """ Then it succeeds -# And IMAP client "1" eventually sees the following message in "INBOX" with this structure: -# """ -# { -# "from": "Foo ", -# "date": "01 Jan 80 00:00 +0000", -# "to": "Bridge Test ", -# "subject": "Embedded message", -# "body-contains": "Hello", -# "content": { -# "content-type": "multipart/mixed", -# "body-contains": "This is a multi-part message in MIME format.", -# "sections":[ -# { -# "content-type": "text/plain", -# "content-type-charset": "utf-8", -# "transfer-encoding": "7bit", -# "body-is": "" -# }, -# { -# "content-type": "message/rfc822", -# "content-type-name": "embedded.eml", -# "transfer-encoding": "7bit", -# "content-disposition": "attachment", -# "content-disposition-filename": "embedded.eml", -# "body-is": "From: Bar \n\rTo: Bridge Test \n\rSubject: (No Subject)\n\rContent-Type: text/plain; charset=utf-8\n\rContent-Transfer-Encoding: quoted-printable\n\r\n\rhello" -# } -# ] -# } -# } -# """ + And IMAP client "1" eventually sees the following message in "INBOX" with this structure: + """ + { + "from": "Foo ", + "date": "01 Jan 80 00:00 +0000", + "to": "Bridge Test ", + "subject": "Embedded message", + "body-contains": "Hello", + "content": { + "content-type": "multipart/mixed", + "sections":[ + { + "body-is": "This is a multi-part message in MIME format." + }, + { + "content-type": "text/plain", + "content-type-charset": "utf-8", + "transfer-encoding": "7bit", + "body-is": "Hello" + }, + { + "content-type": "text/html", + "content-type-charset": "utf-8", + "transfer-encoding": "7bit", + "body-contains": "HELLO" + }, + { + "content-type": "message/rfc822", + "content-type-name": "embedded.eml", + "transfer-encoding": "7bit", + "content-disposition": "attachment", + "content-disposition-filename": "embedded.eml", + "body-is": "From: Bar \nTo: Bridge Test \nSubject: (No Subject)\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nhello" + } + ] + } + } + """ diff --git a/tests/types_test.go b/tests/types_test.go index f7d4526d..78eb7b97 100644 --- a/tests/types_test.go +++ b/tests/types_test.go @@ -207,9 +207,12 @@ func newMessageStructFromIMAP(msg *imap.Message) MessageStruct { panic(err) } var body string - if m.MIMEType == rfc822.TextPlain { + switch { + case m.MIMEType == rfc822.TextPlain: body = strings.TrimSpace(string(m.PlainBody)) - } else { + case m.MIMEType == rfc822.MultipartMixed: + _, body, _ = strings.Cut(string(m.MIMEBody), "\r\n\r\n") + default: body = strings.TrimSpace(string(m.RichBody)) } @@ -221,7 +224,7 @@ func newMessageStructFromIMAP(msg *imap.Message) MessageStruct { CC: formatAddressList(msg.Envelope.Cc), BCC: formatAddressList(msg.Envelope.Bcc), - Content: parseMessageSection(literal, body), + Content: parseMessageSection([]byte(strings.TrimSpace(string(literal))), strings.TrimSpace(body)), } return message } @@ -262,20 +265,30 @@ func parseMessageSection(literal []byte, body string) MessageSection { for id, value := range contentDisposition { if id == 0 { msgSect.ContentDisposition = strings.TrimSpace(string(value)) + continue } param := bytes.Split(value, []byte("=")) if strings.TrimSpace(string(param[0])) == "filename" && len(param) >= 2 { - filename := strings.TrimPrefix(string(value), "filename=") + _, filename, _ := strings.Cut(string(value), "filename=") + filename = strings.Trim(filename, "\"") msgSect.ContentDispositionFilename = strings.TrimSpace(filename) } } if msgSect.ContentTypeBoundary != "" { - sections := bytes.Split([]byte(msgSect.BodyIs), []byte("--"+msgSect.ContentTypeBoundary)) + sections := bytes.Split(literal, []byte("--"+msgSect.ContentTypeBoundary)) // Remove last element that will be the -- from finale boundary sections = sections[:len(sections)-1] + sections = sections[1:] for _, v := range sections { - msgSect.Sections = append(msgSect.Sections, parseMessageSection(v, string(v))) + str := strings.TrimSpace(string(v)) + _, sectionBody, found := strings.Cut(str, "\r\n\r\n") + if !found { + if _, sectionBody, found = strings.Cut(str, "\n\n"); !found { + sectionBody = str + } + } + msgSect.Sections = append(msgSect.Sections, parseMessageSection([]byte(str), strings.TrimSpace(sectionBody))) } } return msgSect @@ -367,18 +380,20 @@ func matchContent(have MessageSection, want MessageSection) bool { if want.TransferEncoding != "" && want.TransferEncoding != have.TransferEncoding { return false } - if want.BodyContains != "" && strings.Contains(have.BodyIs, want.BodyContains) { + if want.BodyContains != "" && !strings.Contains(strings.TrimSpace(have.BodyIs), strings.TrimSpace(want.BodyContains)) { return false } - if want.BodyIs != "" && want.BodyIs != have.BodyIs { + if want.BodyIs != "" && strings.TrimSpace(have.BodyIs) != strings.TrimSpace(want.BodyIs) { return false } - for _, section := range want.Sections { - if !matchContent(have, section) { + if len(have.Sections) != len(want.Sections) { + return false + } + for i, section := range want.Sections { + if !matchContent(have.Sections[i], section) { return false } } - return true } @@ -536,7 +551,3 @@ type Contact struct { Sign string `bdd:"signature"` Encrypt string `bdd:"encryption"` } - -func FullAddress(addr *imap.Address) string { - return addr.PersonalName + " <" + addr.MailboxName + "@" + addr.HostName + ">" -} From 8402657108f39553c684175bf693d0919cd51d69 Mon Sep 17 00:00:00 2001 From: Romain Le Jeune Date: Fri, 29 Sep 2023 08:35:41 +0000 Subject: [PATCH 80/93] fix(GODT-2968): use proper base64 encoded string even for bad password test. --- tests/imap_test.go | 5 +++-- tests/smtp_test.go | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/imap_test.go b/tests/imap_test.go index 4165984f..8138a9c9 100644 --- a/tests/imap_test.go +++ b/tests/imap_test.go @@ -19,6 +19,7 @@ package tests import ( "bytes" + "encoding/base64" "encoding/json" "fmt" "io" @@ -118,8 +119,8 @@ func (s *scenario) imapClientCannotAuthenticateWithIncorrectUsername(clientID st func (s *scenario) imapClientCannotAuthenticateWithIncorrectPassword(clientID string) error { userID, client := s.t.getIMAPClient(clientID) - - if err := client.Login(s.t.getUserByID(userID).getEmails()[0], s.t.getUserByID(userID).getBridgePass()+"bad"); err == nil { + badPass := base64.StdEncoding.EncodeToString([]byte("bad_password")) + if err := client.Login(s.t.getUserByID(userID).getEmails()[0], badPass); err == nil { return fmt.Errorf("expected error, got nil") } diff --git a/tests/smtp_test.go b/tests/smtp_test.go index c5d48f66..4a986837 100644 --- a/tests/smtp_test.go +++ b/tests/smtp_test.go @@ -18,6 +18,7 @@ package tests import ( + "encoding/base64" "fmt" "net/smtp" "os" @@ -84,8 +85,8 @@ func (s *scenario) smtpClientCannotAuthenticateWithIncorrectUsername(clientID st func (s *scenario) smtpClientCannotAuthenticateWithIncorrectPassword(clientID string) error { userID, client := s.t.getSMTPClient(clientID) - - if err := client.Auth(smtp.PlainAuth("", s.t.getUserByID(userID).getEmails()[0], s.t.getUserByID(userID).getBridgePass()+"bad", constants.Host)); err == nil { + badPass := base64.StdEncoding.EncodeToString([]byte("bad_password")) + if err := client.Auth(smtp.PlainAuth("", s.t.getUserByID(userID).getEmails()[0], badPass, constants.Host)); err == nil { return fmt.Errorf("expected error, got nil") } From 55a9d4973c6f9627a6883d68f8d9ecda01a74eda Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Fri, 29 Sep 2023 15:24:35 +0200 Subject: [PATCH 81/93] fix(GODT-2988): fix setup wizard KB links. --- .../bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index c63cec46..5dd22bf8 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -26,7 +26,7 @@ Item { function showAppleMailAutoconfigCertificateInstall() { showAppleMailAutoconfigCommon(); descriptionLabel.text = qsTr("Apple Mail configuration is mostly automated, but in order to work, Bridge needs to install a certificate in your keychain."); - linkLabel1.setCallback(function() { Backend.openKBArticle("https://proton.me/support/bridge/apple-mail-certificate"); }, qsTr("Why is this certificate needed?"), true); + linkLabel1.setCallback(function() { Backend.openKBArticle("https://proton.me/support/apple-mail-certificate"); }, qsTr("Why is this certificate needed?"), true); linkLabel2.clear(); } function showAppleMailAutoconfigCommon() { @@ -40,7 +40,7 @@ Item { function showAppleMailAutoconfigProfileInstall() { showAppleMailAutoconfigCommon(); descriptionLabel.text = qsTr("The final step before you can start using Apple Mail is to install the Bridge server profile in the system preferences.\n\nAdding a server profile is necessary to ensure that your Mac can receive and send Proton Mails."); - linkLabel1.setCallback(function() { Backend.openKBArticle("https://proton.me/support/bridge/macos-certificate-warning"); }, qsTr("Why is there a yellow warning sign?"), true); + linkLabel1.setCallback(function() { Backend.openKBArticle("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); } function showClientSelector(newAccount = true) { @@ -64,7 +64,7 @@ Item { function showOnboarding() { titleLabel.text = (Backend.users.count === 0) ? qsTr("Welcome to\nProton Mail Bridge") : qsTr("Add a Proton Mail account"); descriptionLabel.text = qsTr("Bridge is the gateway between your Proton account and your email client. It runs in the background and encrypts and decrypts your messages seamlessly. "); - linkLabel1.setCallback(function() { Backend.openKBArticle("https://proton.me/support/bridge/why-you-need-bridge"); }, qsTr("Why do I need Bridge?"), true); + linkLabel1.setCallback(function() { Backend.openKBArticle("https://proton.me/support/why-you-need-bridge"); }, qsTr("Why do I need Bridge?"), true); linkLabel2.clear(); root.iconSource = "/qml/icons/img-welcome.svg"; root.iconHeight = 148; From 742d9eeef308fafc281ca3de3f298b0c166cc84c Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Fri, 29 Sep 2023 14:57:15 +0200 Subject: [PATCH 82/93] feat(GODT-2960): added content in empty view when there is no account. --- .../bridge-gui/bridge-gui/Resources.qrc | 1 + .../bridge-gui/qml/ContentWrapper.qml | 37 +++++++----- .../bridge-gui/qml/NoAccountView.qml | 56 +++++++++++++++++++ .../bridge-gui/qml/SetupWizard/LeftPane.qml | 12 ++-- 4 files changed, 88 insertions(+), 18 deletions(-) create mode 100644 internal/frontend/bridge-gui/bridge-gui/qml/NoAccountView.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index 19126b21..9403ac97 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -81,6 +81,7 @@ qml/KeychainSettings.qml qml/LocalCacheSettings.qml qml/MainWindow.qml + qml/NoAccountView.qml qml/NotificationDialog.qml qml/NotificationPopups.qml qml/Notifications/Notification.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml b/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml index 4b7a7fa4..31cbe99e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml @@ -324,23 +324,32 @@ Item { anchors.fill: parent - AccountView { + StackLayout { // 0 - colorScheme: root.colorScheme - notifications: root.notifications - user: { - if (accounts.currentIndex < 0) - return undefined; - if (Backend.users.count === 0) - return undefined; - return Backend.users.get(accounts.currentIndex); + currentIndex: (Backend.users.count > 0 ? 1 : 0) + NoAccountView { + colorScheme: root.colorScheme + onLinkClicked: function() { + root.showLogin("") + } } + AccountView { + colorScheme: root.colorScheme + notifications: root.notifications + user: { + if (accounts.currentIndex < 0) + return undefined; + if (Backend.users.count === 0) + return undefined; + return Backend.users.get(accounts.currentIndex); + } - onShowClientConfigurator: function (user, address, justLoggedIn) { - root.showClientConfigurator(user, address, justLoggedIn); - } - onShowLogin: function (username) { - root.showLogin(username); + onShowClientConfigurator: function (user, address, justLoggedIn) { + root.showClientConfigurator(user, address, justLoggedIn); + } + onShowLogin: function (username) { + root.showLogin(username); + } } } Rectangle { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/NoAccountView.qml b/internal/frontend/bridge-gui/bridge-gui/qml/NoAccountView.qml new file mode 100644 index 00000000..0dab4de4 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/NoAccountView.qml @@ -0,0 +1,56 @@ +// Copyright (c) 2023 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 . +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Proton +import "SetupWizard" + +Rectangle { + id: root + + property ColorScheme colorScheme + + color: root.colorScheme.background_norm + + signal linkClicked() + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + // we use the setup wizard left pane (onboarding version) + LeftPane { + Layout.alignment: Qt.AlignHCenter + Layout.fillHeight: true + Layout.preferredWidth: ProtonStyle.wizard_pane_width + colorScheme: root.colorScheme + wizard: setupWizard + + Component.onCompleted: { + showOnboarding(); + link1.setCallback(root.linkClicked, "Start setup", false) + } + } + Image { + id: mailLogoWithWordmark + Layout.alignment: Qt.AlignHCenter + Layout.bottomMargin: ProtonStyle.wizard_window_margin + height: sourceSize.height + source: root.colorScheme.mail_logo_with_wordmark + sourceSize.height: 36 + sourceSize.width: 134 + width: sourceSize.width + } + } +} \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 5dd22bf8..433c1ac1 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -22,6 +22,10 @@ Item { property string iconSource property int iconWidth property var wizard + property ColorScheme colorScheme + property var _colorScheme: wizard ? wizard.colorScheme : colorScheme + property var link1: linkLabel1 + property var link2: linkLabel2 function showAppleMailAutoconfigCertificateInstall() { showAppleMailAutoconfigCommon(); @@ -100,7 +104,7 @@ Item { id: titleLabel Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - colorScheme: wizard.colorScheme + colorScheme: _colorScheme horizontalAlignment: Text.AlignHCenter text: "" type: Label.LabelType.Heading @@ -111,7 +115,7 @@ Item { id: descriptionLabel Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - colorScheme: wizard.colorScheme + colorScheme: _colorScheme horizontalAlignment: Text.AlignHCenter text: "" type: Label.LabelType.Body @@ -120,13 +124,13 @@ Item { LinkLabel { id: linkLabel1 Layout.alignment: Qt.AlignHCenter - colorScheme: wizard.colorScheme + colorScheme: _colorScheme visible: (text !== "") } LinkLabel { id: linkLabel2 Layout.alignment: Qt.AlignHCenter - colorScheme: wizard.colorScheme + colorScheme: _colorScheme visible: (text !== "") } } From 52addb2582aabaf01640e0d58e36703c61c2a0d3 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Fri, 29 Sep 2023 17:35:36 +0200 Subject: [PATCH 83/93] feat(GODT-2960): replaced the account list with a button and label when no account is configured. --- .../bridge-gui/qml/ContentWrapper.qml | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml b/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml index 31cbe99e..e37e2596 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml @@ -54,6 +54,10 @@ Item { rightContent.showGeneralSettings(); } + function hasAccount() { + return Backend.users.count > 0 + } + RowLayout { anchors.fill: parent spacing: 0 @@ -190,6 +194,41 @@ Item { Layout.minimumHeight: 1 color: leftBar.colorScheme.border_weak } + Item { + id: noAccountBox + + Layout.fillHeight: true + Layout.fillWidth: true + Layout.topMargin: 24 + visible: !hasAccount() + + ColumnLayout { + anchors.fill: parent + spacing: 8 + + Label { + colorScheme: leftBar.colorScheme + color: colorScheme.text_weak + Layout.alignment: Qt.AlignHCenter + text: qsTr("No accounts") + } + Button { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + colorScheme: leftBar.colorScheme + text: qsTr("Add an account") + secondary: true + onClicked: root.showLogin("") + } + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } + + ListView { id: accounts @@ -206,7 +245,7 @@ Item { clip: true model: Backend.users spacing: 12 - + visible: hasAccount() delegate: 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 @@ -326,7 +365,7 @@ Item { StackLayout { // 0 - currentIndex: (Backend.users.count > 0 ? 1 : 0) + currentIndex: hasAccount() ? 1 : 0 NoAccountView { colorScheme: root.colorScheme onLinkClicked: function() { From 48d1ca1e72390fb82211b1b3883e09a6c85035a5 Mon Sep 17 00:00:00 2001 From: Romain Le Jeune Date: Mon, 2 Oct 2023 13:34:40 +0000 Subject: [PATCH 84/93] fix(GODT-2989): allow to send bug report when no account connected. --- internal/bridge/bug_report.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/bridge/bug_report.go b/internal/bridge/bug_report.go index 49675337..2bcfba71 100644 --- a/internal/bridge/bug_report.go +++ b/internal/bridge/bug_report.go @@ -34,7 +34,7 @@ const ( ) func (bridge *Bridge) ReportBug(ctx context.Context, osType, osVersion, title, description, username, email, client string, attachLogs bool) error { - var account string + var account = username if info, err := bridge.QueryUserInfo(username); err == nil { account = info.Username From 0c212fbef41d2dbcbfce37ed78fba79361cc1459 Mon Sep 17 00:00:00 2001 From: Jakub Date: Mon, 2 Oct 2023 16:31:07 +0200 Subject: [PATCH 85/93] chore: Vasco da Gama Bridge 3.6.0 changelog. --- Changelog.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Changelog.md b/Changelog.md index 5257eb13..06759abe 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,8 +10,10 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) * GODT-2772: Setup wizard content. * GODT-2769: Setup Wizard architecture. * GODT-2767: Setup Wizard foundations. +* GODT-2725: Implement receive message step with expected structure exposed. ### Changed +* GODT-2960: Added content in empty view when there is no account. * GODT-2771: Cert related tools for macOS. * GODT-2770: Proof of concept for web view as a tool window and overlay (not used). * GODT-2916: Split Decryption from Message Building. @@ -19,6 +21,13 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) * GODT-2664: Trigger QA installer. ### Fixed +* GODT-2989: Allow to send bug report when no account connected. +* GODT-2988: Fix setup wizard KB links. +* GODT-2968: Use proper base64 encoded string even for bad password test. +* GODT-2965: Fix multipart/mixed testdata + structure parsing steps related to this. +* GODT-2932: Fix syncing not being reported in GUI. +* GODT-2967: Tray menu entries close the setup wizard when needed. +* GODT-2212: Preserver Header order in message building. * Fixed missing GoOs gRPC call in bridge-gui-tester. * GODT-2929: Message dedup with different text transfer encoding. From 80c852a5b234e1f7a7a0d17383dd87f1fb719461 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Tue, 3 Oct 2023 10:48:46 +0200 Subject: [PATCH 86/93] fix(GODT-2992): fix link in 'no account view' in main window after 2FA or TOTP are cancelled. (cherry picked from commit 1c344211d1ab436c80b4c0c185dc34af45d074e8) --- .../bridge-gui/qml/SetupWizard/LeftPane.qml | 10 ---------- .../bridge-gui/qml/SetupWizard/SetupWizard.qml | 11 +++++++++++ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml index 433c1ac1..e809aa98 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -75,16 +75,6 @@ Item { root.iconWidth = 265; } - Connections { - function onLogin2FARequested() { - showLogin2FA(); - } - function onLogin2PasswordRequested() { - showLoginMailboxPassword(); - } - - target: Backend - } ColumnLayout { anchors.left: parent.left anchors.right: parent.right diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml index 3248184a..ae384e0e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -186,6 +186,17 @@ Item { target: clientConfigAppleMail } + + Connections { + function onLogin2FARequested() { + leftContent.showLogin2FA(); + } + function onLogin2PasswordRequested() { + leftContent.showLoginMailboxPassword(); + } + + target: Backend + } } Image { id: mailLogoWithWordmark From d3582fa981a8f13f8a8a360d72d661604ac5a477 Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 3 Oct 2023 16:43:33 +0200 Subject: [PATCH 87/93] chore: Vasco da Gama Bridge 3.6.0 changelog. --- Changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 06759abe..5e4ba62d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,7 +2,6 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) - ## Vasco da Gama Bridge 3.6.0 ### Added @@ -21,6 +20,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) * GODT-2664: Trigger QA installer. ### Fixed +* GODT-2992: Fix link in 'no account view' in main window after 2FA or TOTP are cancelled. * GODT-2989: Allow to send bug report when no account connected. * GODT-2988: Fix setup wizard KB links. * GODT-2968: Use proper base64 encoded string even for bad password test. From ba65ffdbc7e85a49262bfff15e96d06cc9dded10 Mon Sep 17 00:00:00 2001 From: Jakub Date: Mon, 9 Oct 2023 13:25:44 +0200 Subject: [PATCH 88/93] chore: Umshiang Bridge 3.5.2 changelog. --- Changelog.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Changelog.md b/Changelog.md index 5e4ba62d..e033fbb2 100644 --- a/Changelog.md +++ b/Changelog.md @@ -33,6 +33,13 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) +## Umshiang Bridge 3.5.2 + +### Fixed +* GODT-3003: Ensure IMAP State is reset after vault corruption. +* GODT-3001: Only create system labels during system label sync. + + ## Umshiang Bridge 3.5.1 ### Fixed From cf9651bb9498d025b1e1697b97d448ed74e267a7 Mon Sep 17 00:00:00 2001 From: Leander Beernaert Date: Fri, 6 Oct 2023 10:09:10 +0100 Subject: [PATCH 89/93] fix(GODT-3001): Only create system labels during system label sync --- internal/services/imapservice/sync_update_applier.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/services/imapservice/sync_update_applier.go b/internal/services/imapservice/sync_update_applier.go index 58230ae5..4ba043b1 100644 --- a/internal/services/imapservice/sync_update_applier.go +++ b/internal/services/imapservice/sync_update_applier.go @@ -119,6 +119,10 @@ func (s *SyncUpdateApplier) SyncSystemLabelsOnly(ctx context.Context, labels map continue } + if label.Type != proton.LabelTypeSystem { + continue + } + for _, c := range connectors { update := newSystemMailboxCreatedUpdate(imap.MailboxID(label.ID), label.Name) updates = append(updates, update) From bf244e5c863671bad0c131c515cf3b1c05622f68 Mon Sep 17 00:00:00 2001 From: Leander Beernaert Date: Fri, 6 Oct 2023 15:00:36 +0100 Subject: [PATCH 90/93] fix(GODT-3003): Ensure IMAP State is reset after vault corruption After we detect that the user has suffered the GODT-3003 bug due the vault corruption not ensuring that a previous sync state would be erased, we patch the gluon db directly and then reset the sync state. After the account is added, the sync is automatically triggered and the account state fixes itself. --- Makefile | 1 + go.mod | 2 +- go.sum | 4 +- internal/bridge/bridge_test.go | 2 +- internal/bridge/sync_test.go | 62 ++++++ internal/services/imapservice/connector.go | 71 +++++- .../services/imapservice/connector_test.go | 205 ++++++++++++++++++ internal/services/imapservice/mocks/mocks.go | 138 ++++++++++++ internal/services/imapservice/service.go | 6 +- .../imapservice/service_address_events.go | 1 + .../imapservice/sync_state_provider.go | 4 +- .../imapservice/sync_state_provider_test.go | 4 +- internal/services/imapsmtpserver/service.go | 5 + 13 files changed, 493 insertions(+), 12 deletions(-) create mode 100644 internal/services/imapservice/connector_test.go create mode 100644 internal/services/imapservice/mocks/mocks.go diff --git a/Makefile b/Makefile index 462ce114..0ae1998a 100644 --- a/Makefile +++ b/Makefile @@ -304,6 +304,7 @@ ApplyStageInput,BuildStageInput,BuildStageOutput,DownloadStageInput,DownloadStag StateProvider,Regulator,UpdateApplier,MessageBuilder,APIClient,Reporter,DownloadRateModifier \ > tmp mv tmp internal/services/syncservice/mocks_test.go + mockgen --package mocks github.com/ProtonMail/gluon/connector IMAPStateWrite > internal/services/imapservice/mocks/mocks.go lint: gofiles lint-golang lint-license lint-dependencies lint-changelog lint-bug-report diff --git a/go.mod b/go.mod index a1a498b0..fc5a08aa 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 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.20230911134257-5eb2eeebbef5 + github.com/ProtonMail/gluon v0.17.1-0.20231009084701-3af0474b0b3c github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a github.com/ProtonMail/go-proton-api v0.4.1-0.20230925123025-331ad8e6d5ee github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton diff --git a/go.sum b/go.sum index 4c9c029b..cb4e1e30 100644 --- a/go.sum +++ b/go.sum @@ -23,8 +23,8 @@ github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo= github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk= github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g= -github.com/ProtonMail/gluon v0.17.1-0.20230911134257-5eb2eeebbef5 h1:O4BusNL870VgVVDSUX2Oaz8A/fNtJhakUKwx0YBIdn8= -github.com/ProtonMail/gluon v0.17.1-0.20230911134257-5eb2eeebbef5/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo= +github.com/ProtonMail/gluon v0.17.1-0.20231009084701-3af0474b0b3c h1:gUDu4pOswgbou0QczfreNiXQFrmvVlpSh8Q+vft/JvI= +github.com/ProtonMail/gluon v0.17.1-0.20231009084701-3af0474b0b3c/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo= github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4= github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4= github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= diff --git a/internal/bridge/bridge_test.go b/internal/bridge/bridge_test.go index acd0fd0c..6ce8b1bd 100644 --- a/internal/bridge/bridge_test.go +++ b/internal/bridge/bridge_test.go @@ -585,7 +585,7 @@ func TestBridge_MissingGluonStore(t *testing.T) { require.NoError(t, os.RemoveAll(gluonDir)) // Bridge starts but can't find the gluon store dir; there should be no error. - withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridgeWaitForServers(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // ... }) }) diff --git a/internal/bridge/sync_test.go b/internal/bridge/sync_test.go index 1d613bf3..07c0988a 100644 --- a/internal/bridge/sync_test.go +++ b/internal/bridge/sync_test.go @@ -37,6 +37,7 @@ import ( "github.com/ProtonMail/proton-bridge/v3/internal/bridge" "github.com/ProtonMail/proton-bridge/v3/internal/constants" "github.com/ProtonMail/proton-bridge/v3/internal/events" + "github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice" "github.com/bradenaw/juniper/iterator" "github.com/bradenaw/juniper/stream" "github.com/bradenaw/juniper/xslices" @@ -579,6 +580,67 @@ func TestBridge_MessageCreateDuringSync(t *testing.T) { }, server.WithTLS(false)) } +func TestBridge_CorruptedVaultClearsPreviousIMAPSyncState(t *testing.T) { + withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) { + userID, addrID, err := s.CreateUser("imap", password) + require.NoError(t, err) + + labelID, err := s.CreateLabel(userID, "folder", "", proton.LabelTypeFolder) + require.NoError(t, err) + + withClient(ctx, t, s, "imap", password, func(ctx context.Context, c *proton.Client) { + createNumMessages(ctx, t, c, addrID, labelID, 100) + }) + + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{})) + defer done() + + var err error + + userID, err = bridge.LoginFull(context.Background(), "imap", password, nil, nil) + require.NoError(t, err) + + // Wait for sync to finish + require.Equal(t, userID, (<-syncCh).UserID) + }) + + settingsPath, err := locator.ProvideSettingsPath() + require.NoError(t, err) + + syncConfigPath, err := locator.ProvideIMAPSyncConfigPath() + require.NoError(t, err) + + syncStatePath := imapservice.GetSyncConfigPath(syncConfigPath, userID) + // Check sync state is complete + { + state, err := imapservice.NewSyncState(syncStatePath) + require.NoError(t, err) + syncStatus, err := state.GetSyncStatus(context.Background()) + require.NoError(t, err) + require.True(t, syncStatus.IsComplete()) + } + + // corrupt the vault + require.NoError(t, os.WriteFile(filepath.Join(settingsPath, "vault.enc"), []byte("Trash!"), 0o600)) + + // Bridge starts but can't find the gluon database dir; there should be no error. + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + _, err := bridge.LoginFull(context.Background(), "imap", password, nil, nil) + require.NoError(t, err) + }) + + // Check sync state is reset. + { + state, err := imapservice.NewSyncState(syncStatePath) + require.NoError(t, err) + syncStatus, err := state.GetSyncStatus(context.Background()) + require.NoError(t, err) + require.False(t, syncStatus.IsComplete()) + } + }) +} + func withClient(ctx context.Context, t *testing.T, s *server.Server, username string, password []byte, fn func(context.Context, *proton.Client)) { //nolint:unparam m := proton.New( proton.WithHostURL(s.GetHostURL()), diff --git a/internal/services/imapservice/connector.go b/internal/services/imapservice/connector.go index 04ff2ab9..6e9dc031 100644 --- a/internal/services/imapservice/connector.go +++ b/internal/services/imapservice/connector.go @@ -63,6 +63,7 @@ type Connector struct { log *logrus.Entry sharedCache *SharedCache + syncState *SyncState } func NewConnector( @@ -75,6 +76,7 @@ func NewConnector( panicHandler async.PanicHandler, telemetry Telemetry, showAllMail bool, + syncState *SyncState, ) *Connector { userID := identityState.UserID() @@ -106,6 +108,7 @@ func NewConnector( }), sharedCache: NewSharedCached(), + syncState: syncState, } } @@ -114,9 +117,35 @@ func (s *Connector) StateClose() { s.updateCh.CloseAndDiscardQueued() } -func (s *Connector) Init(_ context.Context, cache connector.IMAPState) error { +func (s *Connector) Init(ctx context.Context, cache connector.IMAPState) error { s.sharedCache.Set(cache) - return nil + + return cache.Write(ctx, func(ctx context.Context, write connector.IMAPStateWrite) error { + rd := s.labels.Read() + defer rd.Close() + + mboxes, err := write.GetMailboxesWithoutAttrib(ctx) + if err != nil { + return err + } + + // Attempt to fix bug when a vault got corrupted, but the sync state did not get reset leading to + // all labels being written to the root level. If we detect this happened, reset the sync state. + { + applied, err := fixGODT3003Labels(ctx, s.log, mboxes, rd, write) + if err != nil { + return err + } + + if applied { + s.log.Debug("Patched folders/labels after GODT-3003 incident, resetting sync state.") + if err := s.syncState.ClearSyncStatus(ctx); err != nil { + return err + } + } + } + return nil + }) } func (s *Connector) Authorize(ctx context.Context, username string, password []byte) bool { @@ -745,3 +774,41 @@ func (s *Connector) createDraft(ctx context.Context, literal []byte, addrKR *cry func (s *Connector) publishUpdate(_ context.Context, update imap.Update) { s.updateCh.Enqueue(update) } + +func fixGODT3003Labels( + ctx context.Context, + log *logrus.Entry, + mboxes []imap.MailboxNoAttrib, + rd labelsRead, + write connector.IMAPStateWrite, +) (bool, error) { + var applied bool + for _, mbox := range mboxes { + lbl, ok := rd.GetLabel(string(mbox.ID)) + if !ok { + continue + } + + if lbl.Type == proton.LabelTypeFolder { + if mbox.Name[0] != folderPrefix { + log.WithField("labelID", mbox.ID.ShortID()).Debug("Found folder without prefix, patching") + if err := write.PatchMailboxHierarchyWithoutTransforms(ctx, mbox.ID, xslices.Insert(mbox.Name, 0, folderPrefix)); err != nil { + return false, fmt.Errorf("failed to update mailbox name: %w", err) + } + + applied = true + } + } else if lbl.Type == proton.LabelTypeLabel { + if mbox.Name[0] != labelPrefix { + log.WithField("labelID", mbox.ID.ShortID()).Debug("Found label without prefix, patching") + if err := write.PatchMailboxHierarchyWithoutTransforms(ctx, mbox.ID, xslices.Insert(mbox.Name, 0, labelPrefix)); err != nil { + return false, fmt.Errorf("failed to update mailbox name: %w", err) + } + + applied = true + } + } + } + + return applied, nil +} diff --git a/internal/services/imapservice/connector_test.go b/internal/services/imapservice/connector_test.go new file mode 100644 index 00000000..f79b885d --- /dev/null +++ b/internal/services/imapservice/connector_test.go @@ -0,0 +1,205 @@ +// Copyright (c) 2023 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 . + +package imapservice + +import ( + "context" + "testing" + + "github.com/ProtonMail/gluon/imap" + "github.com/ProtonMail/go-proton-api" + "github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice/mocks" + "github.com/golang/mock/gomock" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/require" +) + +func TestFixGODT3003Labels(t *testing.T) { + mockCtrl := gomock.NewController(t) + + log := logrus.WithField("test", "test") + + sharedLabels := newRWLabels() + wr := sharedLabels.Write() + wr.SetLabel("foo", proton.Label{ + ID: "foo", + ParentID: "bar", + Name: "Foo", + Path: []string{"bar", "Foo"}, + Color: "", + Type: proton.LabelTypeFolder, + }) + + wr.SetLabel("0", proton.Label{ + ID: "0", + ParentID: "", + Name: "Inbox", + Path: []string{"Inbox"}, + Color: "", + Type: proton.LabelTypeSystem, + }) + + wr.SetLabel("bar", proton.Label{ + ID: "bar", + ParentID: "", + Name: "boo", + Path: []string{"bar"}, + Color: "", + Type: proton.LabelTypeFolder, + }) + + wr.SetLabel("my_label", proton.Label{ + ID: "my_label", + ParentID: "", + Name: "MyLabel", + Path: []string{"MyLabel"}, + Color: "", + Type: proton.LabelTypeLabel, + }) + + wr.SetLabel("my_label2", proton.Label{ + ID: "my_label2", + ParentID: "", + Name: "MyLabel2", + Path: []string{labelPrefix, "MyLabel2"}, + Color: "", + Type: proton.LabelTypeLabel, + }) + wr.Close() + + mboxs := []imap.MailboxNoAttrib{ + { + ID: "0", + Name: []string{"Inbox"}, + }, + { + ID: "bar", + Name: []string{"bar"}, + }, + { + ID: "foo", + Name: []string{"bar", "Foo"}, + }, + { + ID: "my_label", + Name: []string{"MyLabel"}, + }, + { + ID: "my_label2", + Name: []string{labelPrefix, "MyLabel2"}, + }, + } + + rd := sharedLabels.Read() + defer rd.Close() + + imapState := mocks.NewMockIMAPStateWrite(mockCtrl) + + imapState.EXPECT().PatchMailboxHierarchyWithoutTransforms(gomock.Any(), gomock.Eq(imap.MailboxID("bar")), gomock.Eq([]string{folderPrefix, "bar"})) + imapState.EXPECT().PatchMailboxHierarchyWithoutTransforms(gomock.Any(), gomock.Eq(imap.MailboxID("foo")), gomock.Eq([]string{folderPrefix, "bar", "Foo"})) + imapState.EXPECT().PatchMailboxHierarchyWithoutTransforms(gomock.Any(), gomock.Eq(imap.MailboxID("my_label")), gomock.Eq([]string{labelPrefix, "MyLabel"})) + + applied, err := fixGODT3003Labels(context.Background(), log, mboxs, rd, imapState) + require.NoError(t, err) + require.True(t, applied) +} + +func TestFixGODT3003Labels_Noop(t *testing.T) { + mockCtrl := gomock.NewController(t) + + log := logrus.WithField("test", "test") + + sharedLabels := newRWLabels() + wr := sharedLabels.Write() + wr.SetLabel("foo", proton.Label{ + ID: "foo", + ParentID: "bar", + Name: "Foo", + Path: []string{folderPrefix, "bar", "Foo"}, + Color: "", + Type: proton.LabelTypeFolder, + }) + + wr.SetLabel("0", proton.Label{ + ID: "0", + ParentID: "", + Name: "Inbox", + Path: []string{"Inbox"}, + Color: "", + Type: proton.LabelTypeSystem, + }) + + wr.SetLabel("bar", proton.Label{ + ID: "bar", + ParentID: "", + Name: "bar", + Path: []string{folderPrefix, "bar"}, + Color: "", + Type: proton.LabelTypeFolder, + }) + + wr.SetLabel("my_label", proton.Label{ + ID: "my_label", + ParentID: "", + Name: "MyLabel", + Path: []string{labelPrefix, "MyLabel"}, + Color: "", + Type: proton.LabelTypeLabel, + }) + + wr.SetLabel("my_label2", proton.Label{ + ID: "my_label2", + ParentID: "", + Name: "MyLabel2", + Path: []string{labelPrefix, "MyLabel2"}, + Color: "", + Type: proton.LabelTypeLabel, + }) + wr.Close() + + mboxs := []imap.MailboxNoAttrib{ + { + ID: "0", + Name: []string{"Inbox"}, + }, + { + ID: "bar", + Name: []string{folderPrefix, "bar"}, + }, + { + ID: "foo", + Name: []string{folderPrefix, "bar", "Foo"}, + }, + { + ID: "my_label", + Name: []string{labelPrefix, "MyLabel"}, + }, + { + ID: "my_label2", + Name: []string{labelPrefix, "MyLabel2"}, + }, + } + + rd := sharedLabels.Read() + defer rd.Close() + + imapState := mocks.NewMockIMAPStateWrite(mockCtrl) + applied, err := fixGODT3003Labels(context.Background(), log, mboxs, rd, imapState) + require.NoError(t, err) + require.False(t, applied) +} diff --git a/internal/services/imapservice/mocks/mocks.go b/internal/services/imapservice/mocks/mocks.go new file mode 100644 index 00000000..17e5b7cd --- /dev/null +++ b/internal/services/imapservice/mocks/mocks.go @@ -0,0 +1,138 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ProtonMail/gluon/connector (interfaces: IMAPStateWrite) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + imap "github.com/ProtonMail/gluon/imap" + gomock "github.com/golang/mock/gomock" +) + +// MockIMAPStateWrite is a mock of IMAPStateWrite interface. +type MockIMAPStateWrite struct { + ctrl *gomock.Controller + recorder *MockIMAPStateWriteMockRecorder +} + +// MockIMAPStateWriteMockRecorder is the mock recorder for MockIMAPStateWrite. +type MockIMAPStateWriteMockRecorder struct { + mock *MockIMAPStateWrite +} + +// NewMockIMAPStateWrite creates a new mock instance. +func NewMockIMAPStateWrite(ctrl *gomock.Controller) *MockIMAPStateWrite { + mock := &MockIMAPStateWrite{ctrl: ctrl} + mock.recorder = &MockIMAPStateWriteMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockIMAPStateWrite) EXPECT() *MockIMAPStateWriteMockRecorder { + return m.recorder +} + +// CreateMailbox mocks base method. +func (m *MockIMAPStateWrite) CreateMailbox(arg0 context.Context, arg1 imap.Mailbox) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateMailbox", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateMailbox indicates an expected call of CreateMailbox. +func (mr *MockIMAPStateWriteMockRecorder) CreateMailbox(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMailbox", reflect.TypeOf((*MockIMAPStateWrite)(nil).CreateMailbox), arg0, arg1) +} + +// GetMailboxCount mocks base method. +func (m *MockIMAPStateWrite) GetMailboxCount(arg0 context.Context) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMailboxCount", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMailboxCount indicates an expected call of GetMailboxCount. +func (mr *MockIMAPStateWriteMockRecorder) GetMailboxCount(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMailboxCount", reflect.TypeOf((*MockIMAPStateWrite)(nil).GetMailboxCount), arg0) +} + +// GetMailboxesWithoutAttrib mocks base method. +func (m *MockIMAPStateWrite) GetMailboxesWithoutAttrib(arg0 context.Context) ([]imap.MailboxNoAttrib, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMailboxesWithoutAttrib", arg0) + ret0, _ := ret[0].([]imap.MailboxNoAttrib) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMailboxesWithoutAttrib indicates an expected call of GetMailboxesWithoutAttrib. +func (mr *MockIMAPStateWriteMockRecorder) GetMailboxesWithoutAttrib(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMailboxesWithoutAttrib", reflect.TypeOf((*MockIMAPStateWrite)(nil).GetMailboxesWithoutAttrib), arg0) +} + +// GetSettings mocks base method. +func (m *MockIMAPStateWrite) GetSettings(arg0 context.Context) (string, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSettings", arg0) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetSettings indicates an expected call of GetSettings. +func (mr *MockIMAPStateWriteMockRecorder) GetSettings(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSettings", reflect.TypeOf((*MockIMAPStateWrite)(nil).GetSettings), arg0) +} + +// PatchMailboxHierarchyWithoutTransforms mocks base method. +func (m *MockIMAPStateWrite) PatchMailboxHierarchyWithoutTransforms(arg0 context.Context, arg1 imap.MailboxID, arg2 []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PatchMailboxHierarchyWithoutTransforms", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// PatchMailboxHierarchyWithoutTransforms indicates an expected call of PatchMailboxHierarchyWithoutTransforms. +func (mr *MockIMAPStateWriteMockRecorder) PatchMailboxHierarchyWithoutTransforms(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PatchMailboxHierarchyWithoutTransforms", reflect.TypeOf((*MockIMAPStateWrite)(nil).PatchMailboxHierarchyWithoutTransforms), arg0, arg1, arg2) +} + +// StoreSettings mocks base method. +func (m *MockIMAPStateWrite) StoreSettings(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StoreSettings", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// StoreSettings indicates an expected call of StoreSettings. +func (mr *MockIMAPStateWriteMockRecorder) StoreSettings(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StoreSettings", reflect.TypeOf((*MockIMAPStateWrite)(nil).StoreSettings), arg0, arg1) +} + +// UpdateMessageFlags mocks base method. +func (m *MockIMAPStateWrite) UpdateMessageFlags(arg0 context.Context, arg1 imap.MessageID, arg2 imap.FlagSet) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateMessageFlags", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateMessageFlags indicates an expected call of UpdateMessageFlags. +func (mr *MockIMAPStateWriteMockRecorder) UpdateMessageFlags(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateMessageFlags", reflect.TypeOf((*MockIMAPStateWrite)(nil).UpdateMessageFlags), arg0, arg1, arg2) +} diff --git a/internal/services/imapservice/service.go b/internal/services/imapservice/service.go index 701a62c6..79e4b9f0 100644 --- a/internal/services/imapservice/service.go +++ b/internal/services/imapservice/service.go @@ -158,7 +158,7 @@ func NewService( syncUpdateApplier: syncUpdateApplier, syncMessageBuilder: syncMessageBuilder, syncReporter: syncReporter, - syncConfigPath: getSyncConfigPath(syncConfigDir, identityState.User.ID), + syncConfigPath: GetSyncConfigPath(syncConfigDir, identityState.User.ID), } } @@ -498,6 +498,7 @@ func (s *Service) buildConnectors() (map[string]*Connector, error) { s.panicHandler, s.telemetry, s.showAllMail, + s.syncStateProvider, ) return connectors, nil @@ -514,6 +515,7 @@ func (s *Service) buildConnectors() (map[string]*Connector, error) { s.panicHandler, s.telemetry, s.showAllMail, + s.syncStateProvider, ) } @@ -644,6 +646,6 @@ type setAddressModeReq struct { type getSyncFailedMessagesReq struct{} -func getSyncConfigPath(path string, userID string) string { +func GetSyncConfigPath(path string, userID string) string { return filepath.Join(path, fmt.Sprintf("sync-%v", userID)) } diff --git a/internal/services/imapservice/service_address_events.go b/internal/services/imapservice/service_address_events.go index 2c5aed9c..78abe4a3 100644 --- a/internal/services/imapservice/service_address_events.go +++ b/internal/services/imapservice/service_address_events.go @@ -128,6 +128,7 @@ func addNewAddressSplitMode(ctx context.Context, s *Service, addrID string) erro s.panicHandler, s.telemetry, s.showAllMail, + s.syncStateProvider, ) if err := s.serverManager.AddIMAPUser(ctx, connector, connector.addrID, s.gluonIDProvider, s.syncStateProvider); err != nil { diff --git a/internal/services/imapservice/sync_state_provider.go b/internal/services/imapservice/sync_state_provider.go index 190d67a5..9b3723d2 100644 --- a/internal/services/imapservice/sync_state_provider.go +++ b/internal/services/imapservice/sync_state_provider.go @@ -220,7 +220,7 @@ func (s *SyncState) loadUnsafe() error { } func DeleteSyncState(configDir, userID string) error { - path := getSyncConfigPath(configDir, userID) + path := GetSyncConfigPath(configDir, userID) if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) { return err @@ -234,7 +234,7 @@ func MigrateVaultSettings( hasLabels, hasMessages bool, failedMessageIDs []string, ) (bool, error) { - filePath := getSyncConfigPath(configDir, userID) + filePath := GetSyncConfigPath(configDir, userID) _, err := os.ReadFile(filePath) //nolint:gosec if err == nil { diff --git a/internal/services/imapservice/sync_state_provider_test.go b/internal/services/imapservice/sync_state_provider_test.go index 0852c53f..e4dd93b7 100644 --- a/internal/services/imapservice/sync_state_provider_test.go +++ b/internal/services/imapservice/sync_state_provider_test.go @@ -29,7 +29,7 @@ import ( func TestMigrateSyncSettings_AlreadyExists(t *testing.T) { tmpDir := t.TempDir() - testFile := getSyncConfigPath(tmpDir, "test") + testFile := GetSyncConfigPath(tmpDir, "test") expected, err := generateTestState(testFile) require.NoError(t, err) @@ -53,7 +53,7 @@ func TestMigrateSyncSettings_DoesNotExist(t *testing.T) { require.NoError(t, err) require.True(t, migrated) - state, err := NewSyncState(getSyncConfigPath(tmpDir, "test")) + state, err := NewSyncState(GetSyncConfigPath(tmpDir, "test")) require.NoError(t, err) status, err := state.GetSyncStatus(context.Background()) require.NoError(t, err) diff --git a/internal/services/imapsmtpserver/service.go b/internal/services/imapsmtpserver/service.go index de186eb9..f2e22176 100644 --- a/internal/services/imapsmtpserver/service.go +++ b/internal/services/imapsmtpserver/service.go @@ -390,6 +390,11 @@ func (sm *Service) handleAddIMAPUserImpl(ctx context.Context, } else { log.Info("Creating new IMAP user") + // GODT-3003: Ensure previous IMAP sync state is cleared if we run into code path after vault corruption. + if err := syncStateProvider.ClearSyncStatus(ctx); err != nil { + return fmt.Errorf("failed to reset sync status: %w", err) + } + gluonID, err := sm.imapServer.AddUser(ctx, connector, idProvider.GluonKey()) if err != nil { return fmt.Errorf("failed to add IMAP user: %w", err) From 275b30e51810be24c4f15a2d8a16269bfd0fa594 Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 10 Oct 2023 11:29:36 +0200 Subject: [PATCH 91/93] chore: Vasco da Gama Bridge 3.6.0 changelog. --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index e033fbb2..8a35fc7e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) + ## Vasco da Gama Bridge 3.6.0 ### Added From e580f89106e8efcb6aa6ea9c4cc1480edd75c6fd Mon Sep 17 00:00:00 2001 From: Jakub Date: Wed, 11 Oct 2023 15:29:52 +0200 Subject: [PATCH 92/93] feat(GODT-3004): update gopenpgp and dependencies. --- go.mod | 6 +++--- go.sum | 20 +++++++++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index fc5a08aa..b6f6097d 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,8 @@ require ( github.com/Masterminds/semver/v3 v3.2.0 github.com/ProtonMail/gluon v0.17.1-0.20231009084701-3af0474b0b3c github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a - github.com/ProtonMail/go-proton-api v0.4.1-0.20230925123025-331ad8e6d5ee - github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton + github.com/ProtonMail/go-proton-api v0.4.1-0.20231011132529-24b5b817ee1f + github.com/ProtonMail/gopenpgp/v2 v2.7.3-proton github.com/PuerkitoBio/goquery v1.8.1 github.com/abiosoft/ishell v2.0.0+incompatible github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37 @@ -53,7 +53,7 @@ require ( require ( github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233 // indirect github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect github.com/ProtonMail/go-srp v0.0.7 // indirect github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect diff --git a/go.sum b/go.sum index cb4e1e30..157d4409 100644 --- a/go.sum +++ b/go.sum @@ -28,19 +28,18 @@ github.com/ProtonMail/gluon v0.17.1-0.20231009084701-3af0474b0b3c/go.mod h1:Og5/ 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-20230322105811-d73448b7e800/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= -github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 h1:ZK3C5DtzV2nVAQTx5S5jQvMeDqWtD1By5mOoyY/xJek= -github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903/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-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.20230925123025-331ad8e6d5ee h1:CzFXOiflEZZqT3HQqj2I5AkIprRbc/c6/lToPdEKzxM= -github.com/ProtonMail/go-proton-api v0.4.1-0.20230925123025-331ad8e6d5ee/go.mod h1:Y3ea3i1UbqHz5vq43odmAAd6lmR4nx0ZIQ32tqMfxTY= +github.com/ProtonMail/go-proton-api v0.4.1-0.20231011132529-24b5b817ee1f h1:n0oBMAz2dJhn5+1WA6NrjkWqkZN+22FQMkPlRwNGhpU= +github.com/ProtonMail/go-proton-api v0.4.1-0.20231011132529-24b5b817ee1f/go.mod h1:ZmvQMA8hanLiD1tFsvu9+qGBcuxbIRfch/4z/nqBhXA= github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI= github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk= -github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton h1:YS6M20yvjCJPR1r4ADW5TPn6rahs4iAyZaACei86bEc= -github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton/go.mod h1:S1lYsaGHykYpxxh2SnJL6ypcAlANKj5NRSY6HxKryKQ= +github.com/ProtonMail/gopenpgp/v2 v2.7.3-proton h1:wuAxBUU9qF2wyDVJprn/2xPDx000eol5gwlKbOUYY88= +github.com/ProtonMail/gopenpgp/v2 v2.7.3-proton/go.mod h1:omVkSsfPAhmptzPF/piMXb16wKIWUvVhZbVW7sJKh0A= github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma7oZPxr03tlmmw= @@ -64,6 +63,7 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm github.com/bradenaw/juniper v0.12.0 h1:Q/7icpPQD1nH/La5DobQfNEtwyrBSiSu47jOQx7lJEM= github.com/bradenaw/juniper v0.12.0/go.mod h1:Z2B7aJlQ7xbfWsnMLROj5t/5FQ94/MkIdKC30J4WvzI= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= 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= @@ -419,6 +419,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U 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-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= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= @@ -466,6 +467,7 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= @@ -514,6 +516,8 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -521,6 +525,7 @@ golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= @@ -531,6 +536,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= From 8be4246f7e261322651a528fcdbe8a9e8c87cb81 Mon Sep 17 00:00:00 2001 From: Jakub Date: Wed, 11 Oct 2023 16:09:55 +0200 Subject: [PATCH 93/93] chore: Vasco da Gama Bridge 3.6.0 changelog. --- Changelog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Changelog.md b/Changelog.md index 8a35fc7e..e2f7059f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -33,6 +33,11 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) * GODT-2929: Message dedup with different text transfer encoding. +## Umshiang Bridge 3.5.3 + +### Changed +* GODT-3004: Update gopenpgp and dependencies. + ## Umshiang Bridge 3.5.2