main: add support for logging out

This commit is contained in:
Tulir Asokan 2024-12-03 23:36:03 +02:00
parent 74439be24c
commit 529ffda4ed
12 changed files with 130 additions and 5 deletions

View file

@ -148,9 +148,9 @@ func main() {
URL: "/",
})
gmx.Client.EventHandler = hicli.JSONEventHandler(func(command *hicli.JSONCommand) {
gmx.SubscribeEvents(nil, func(command *hicli.JSONCommand) {
app.EmitEvent("hicli_event", command)
}).HandleEvent
})
err = app.Run()
if err != nil {

View file

@ -178,6 +178,7 @@ func (gmx *Gomuks) StartClient() {
[]byte("meow"),
hicli.JSONEventHandler(gmx.OnEvent).HandleEvent,
)
gmx.Client.LogoutFunc = gmx.Logout
httpClient := gmx.Client.Client.Client
httpClient.Transport.(*http.Transport).ForceAttemptHTTP2 = false
if !gmx.Config.Matrix.DisableHTTP2 {
@ -246,7 +247,9 @@ func (gmx *Gomuks) SubscribeEvents(closeForRestart WebsocketCloseFunc, cb func(c
gmx.nextListenerID++
id := gmx.nextListenerID
gmx.eventListeners[id] = cb
if closeForRestart != nil {
gmx.websocketClosers[id] = closeForRestart
}
return func() {
gmx.eventListenersLock.Lock()
defer gmx.eventListenersLock.Unlock()

64
pkg/gomuks/logout.go Normal file
View file

@ -0,0 +1,64 @@
// gomuks - A Matrix client written in Go.
// Copyright (C) 2024 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package gomuks
import (
"context"
"errors"
"os"
"path/filepath"
"github.com/rs/zerolog"
"maunium.net/go/mautrix"
)
func (gmx *Gomuks) Logout(ctx context.Context) error {
log := zerolog.Ctx(ctx)
log.Info().Msg("Stopping client and logging out")
gmx.Client.Stop()
_, err := gmx.Client.Client.Logout(ctx)
if err != nil && !errors.Is(err, mautrix.MUnknownToken) {
log.Warn().Err(err).Msg("Failed to log out")
return err
}
log.Info().Msg("Logout complete, removing data")
err = os.RemoveAll(gmx.CacheDir)
if err != nil {
log.Err(err).Str("cache_dir", gmx.CacheDir).Msg("Failed to remove cache dir")
}
if gmx.DataDir == gmx.ConfigDir {
err = os.Remove(filepath.Join(gmx.DataDir, "gomuks.db"))
if err != nil && !errors.Is(err, os.ErrNotExist) {
log.Err(err).Str("data_dir", gmx.DataDir).Msg("Failed to remove database")
}
_ = os.Remove(filepath.Join(gmx.DataDir, "gomuks.db-shm"))
_ = os.Remove(filepath.Join(gmx.DataDir, "gomuks.db-wal"))
} else {
err = os.RemoveAll(gmx.DataDir)
if err != nil {
log.Err(err).Str("data_dir", gmx.DataDir).Msg("Failed to remove data dir")
}
}
log.Info().Msg("Re-initializing directories")
gmx.InitDirectories()
log.Info().Msg("Restarting client")
gmx.StartClient()
gmx.Client.EventHandler(gmx.Client.State())
gmx.Client.EventHandler(gmx.Client.SyncStatus.Load())
log.Info().Msg("Client restarted")
return nil
}

View file

@ -32,6 +32,7 @@ import (
type HiClient struct {
DB *database.Database
CryptoDB *dbutil.Database
Account *database.Account
Client *mautrix.Client
Crypto *crypto.OlmMachine
@ -50,6 +51,7 @@ type HiClient struct {
lastSync time.Time
EventHandler func(evt any)
LogoutFunc func(context.Context) error
firstSyncReceived bool
syncingID int
@ -90,6 +92,9 @@ func New(rawDB, cryptoDB *dbutil.Database, log zerolog.Logger, pickleKey []byte,
EventHandler: evtHandler,
}
if cryptoDB != rawDB {
c.CryptoDB = cryptoDB
}
c.SyncStatus.Store(syncWaiting)
c.ClientStore = &database.ClientStateStore{Database: db}
c.Client = &mautrix.Client{
@ -267,4 +272,10 @@ func (h *HiClient) Stop() {
if err != nil {
h.Log.Err(err).Msg("Failed to close database cleanly")
}
if h.CryptoDB != nil {
err = h.CryptoDB.Close()
if err != nil {
h.Log.Err(err).Msg("Failed to close crypto database cleanly")
}
}
}

View file

@ -114,6 +114,11 @@ func (h *HiClient) handleJSONCommand(ctx context.Context, req *JSONCommand) (any
return unmarshalAndCall(req.Data, func(params *resolveAliasParams) (*mautrix.RespAliasResolve, error) {
return h.Client.ResolveAlias(ctx, params.Alias)
})
case "logout":
if h.LogoutFunc == nil {
return nil, errors.New("logout not supported")
}
return true, h.LogoutFunc(ctx)
case "login":
return unmarshalAndCall(req.Data, func(params *loginParams) (bool, error) {
return true, h.LoginPassword(ctx, params.HomeserverURL, params.Username, params.Password)

View file

@ -293,4 +293,10 @@ export default class Client {
room.paginating = false
}
}
async logout() {
await this.rpc.logout()
localStorage.clear()
this.store.clear()
}
}

View file

@ -129,6 +129,10 @@ export default abstract class RPCClient {
}, this.cancelRequest.bind(this, request_id))
}
logout(): Promise<boolean> {
return this.request("logout", {})
}
sendMessage(params: SendMessageParams): Promise<RawDBEvent> {
return this.request("send_message", params)
}

View file

@ -386,4 +386,17 @@ export class StateStore {
}
return { deletedEvents, deletedState } as const
}
clear() {
this.rooms.clear()
this.roomList.emit([])
this.accountData.clear()
this.currentRoomListFilter = ""
this.#frequentlyUsedEmoji = null
this.#emojiPackKeys = null
this.#watchedRoomEmojiPacks = null
this.#personalEmojiPack = null
this.serverPreferenceCache = {}
this.activeRoomID = undefined
}
}

View file

@ -82,7 +82,7 @@ const BeeperLogin = ({ domain, client }: BeeperLoginProps) => {
onChange={onChangeCode}
/>}
<button
className="beeper-login-button"
className="beeper-login-button primary-color-button"
type="submit"
>{requestID ? "Submit Code" : "Request Code"}</button>
{error && <div className="error">

View file

@ -44,7 +44,7 @@ export const VerificationScreen = ({ client, clientState }: LoginScreenProps) =>
value={recoveryKey}
onChange={evt => setRecoveryKey(evt.target.value)}
/>
<button className="mx-login-button" type="submit">Verify</button>
<button className="mx-login-button primary-color-button" type="submit">Verify</button>
</form>
{error && <div className="error">
{error}

View file

@ -72,4 +72,14 @@ div.settings-view {
}
}
}
button.logout {
margin-top: 1rem;
padding: .5rem 1rem;
&:hover, &:focus {
background-color: var(--error-color);
color: var(--inverted-text-color);
}
}
}

View file

@ -259,6 +259,14 @@ const SettingsView = ({ room }: SettingsViewProps) => {
}
}
}, [client, room])
const onClickLogout = useCallback(() => {
if (window.confirm("Really log out and delete all local data?")) {
client.logout().then(
() => console.info("Successfully logged out"),
err => window.alert(`Failed to log out: ${err}`),
)
}
}, [client])
usePreferences(client.store, room)
const globalServer = client.store.serverPreferenceCache
const globalLocal = client.store.localPreferenceCache
@ -293,6 +301,7 @@ const SettingsView = ({ room }: SettingsViewProps) => {
</table>
<CustomCSSInput setPref={setPref} room={room} />
<AppliedSettingsView room={room} />
<button className="logout" onClick={onClickLogout}>Logout</button>
</>
}