connector: fix UserLogin lifecycle during provisioning

Bridge provisioning would crash because we wouldn't thread the necessary
database models through.
This commit is contained in:
Skip R
2025-11-25 14:25:17 -08:00
parent 063b9d00dd
commit ab68fae8dd
2 changed files with 50 additions and 28 deletions

View File

@@ -33,10 +33,11 @@ import (
) )
type DiscordClient struct { type DiscordClient struct {
connector *DiscordConnector connector *DiscordConnector
usersFromReady map[string]*discordgo.User usersFromReady map[string]*discordgo.User
UserLogin *bridgev2.UserLogin UserLogin *bridgev2.UserLogin
Session *discordgo.Session Session *discordgo.Session
hasBegunSyncing bool
} }
func (d *DiscordConnector) LoadUserLogin(ctx context.Context, login *bridgev2.UserLogin) error { func (d *DiscordConnector) LoadUserLogin(ctx context.Context, login *bridgev2.UserLogin) error {
@@ -116,23 +117,6 @@ func (cl *DiscordClient) connect(ctx context.Context) error {
user := cl.Session.State.User user := cl.Session.State.User
log.Info().Str("user_id", user.ID).Str("user_username", user.Username).Msg("Connected to Discord") log.Info().Str("user_id", user.ID).Str("user_username", user.Username).Msg("Connected to Discord")
if cl.UserLogin != nil {
// Feels a bit hacky to check for this here, but it should be true when
// logging in initially. The UserLogin is only ever created if we know
// that we connected successfully. We _do_ know that by now here, but we're
// not tasked with creating the UserLogin; the login code is. Alas.
// FIXME(skip): Avatar.
cl.UserLogin.RemoteProfile = status.RemoteProfile{
Email: user.Email,
Phone: user.Phone,
Name: user.String(),
}
if err := cl.UserLogin.Save(ctx); err != nil {
log.Err(err).Msg("Couldn't save UserLogin after connecting")
}
}
// Stash all of the users we received in READY so we can perform quick lookups // Stash all of the users we received in READY so we can perform quick lookups
// keyed by user ID. // keyed by user ID.
cl.usersFromReady = make(map[string]*discordgo.User) cl.usersFromReady = make(map[string]*discordgo.User)
@@ -140,7 +124,11 @@ func (cl *DiscordClient) connect(ctx context.Context) error {
cl.usersFromReady[user.ID] = user cl.usersFromReady[user.ID] = user
} }
go cl.syncChannels(ctx) // We won't have a UserLogin during provisioning, because the UserLogin can
// only be properly constructed once we know what the Discord user ID is
// (i.e. we have returned from this function). Thus, rely on the login
// process calling this method manually.
cl.BeginSyncingIfUserLoginPresent(ctx)
return nil return nil
} }
@@ -160,6 +148,33 @@ func (d *DiscordClient) LogoutRemote(ctx context.Context) {
d.Disconnect() d.Disconnect()
} }
func (cl *DiscordClient) BeginSyncingIfUserLoginPresent(ctx context.Context) {
if cl.UserLogin == nil {
cl.connector.bridge.Log.Warn().Msg("Not syncing just yet as we don't have a UserLogin")
return
}
if cl.hasBegunSyncing {
cl.connector.bridge.Log.Warn().Msg("Not beginning sync more than once")
return
}
cl.hasBegunSyncing = true
log := cl.UserLogin.Log
user := cl.Session.State.User
// FIXME(skip): Avatar.
cl.UserLogin.RemoteProfile = status.RemoteProfile{
Email: user.Email,
Phone: user.Phone,
Name: user.String(),
}
if err := cl.UserLogin.Save(ctx); err != nil {
log.Err(err).Msg("Couldn't save UserLogin after connecting")
}
go cl.syncChannels(ctx)
}
func (d *DiscordClient) syncChannels(ctx context.Context) { func (d *DiscordClient) syncChannels(ctx context.Context) {
for _, dm := range d.Session.State.PrivateChannels { for _, dm := range d.Session.State.PrivateChannels {
d.UserLogin.Log.Debug().Str("channel_id", dm.ID).Msg("Syncing private channel") d.UserLogin.Log.Debug().Str("channel_id", dm.ID).Msg("Syncing private channel")

View File

@@ -48,13 +48,14 @@ func (d *DiscordConnector) CreateLogin(ctx context.Context, user *bridgev2.User,
return nil, fmt.Errorf("unknown login flow ID") return nil, fmt.Errorf("unknown login flow ID")
} }
return &DiscordLogin{User: user}, nil return &DiscordLogin{connector: d, User: user}, nil
} }
type DiscordLogin struct { type DiscordLogin struct {
User *bridgev2.User connector *DiscordConnector
Token string User *bridgev2.User
Session *discordgo.Session Token string
Session *discordgo.Session
} }
var _ bridgev2.LoginProcessUserInput = (*DiscordLogin)(nil) var _ bridgev2.LoginProcessUserInput = (*DiscordLogin)(nil)
@@ -104,7 +105,8 @@ func (dl *DiscordLogin) SubmitUserInput(ctx context.Context, input map[string]st
} }
client := DiscordClient{ client := DiscordClient{
Session: session, connector: dl.connector,
Session: session,
} }
err = client.connect(ctx) err = client.connect(ctx)
if err != nil { if err != nil {
@@ -123,9 +125,14 @@ func (dl *DiscordLogin) SubmitUserInput(ctx context.Context, input map[string]st
HeartbeatSession: session.HeartbeatSession, HeartbeatSession: session.HeartbeatSession,
}, },
}, &bridgev2.NewLoginParams{ }, &bridgev2.NewLoginParams{
// We already have a Session; call this instead of the connector's main LoadUserLogin method and thread the Session through. // We already have a Session; let's call this instead of the connector's
// main LoadUserLogin method, and thread the Session through.
LoadUserLogin: func(ctx context.Context, login *bridgev2.UserLogin) error { LoadUserLogin: func(ctx context.Context, login *bridgev2.UserLogin) error {
login.Client = &client login.Client = &client
client.UserLogin = login
// Only now that we have a UserLogin can we begin syncing.
client.BeginSyncingIfUserLoginPresent(ctx)
return nil return nil
}, },
DeleteOnConflict: true, DeleteOnConflict: true,