From ab68fae8dd6e135816e92d8e93275452343d1d49 Mon Sep 17 00:00:00 2001 From: Skip R Date: Tue, 25 Nov 2025 14:25:17 -0800 Subject: [PATCH] connector: fix UserLogin lifecycle during provisioning Bridge provisioning would crash because we wouldn't thread the necessary database models through. --- pkg/connector/client.go | 59 ++++++++++++++++++++++++++--------------- pkg/connector/login.go | 19 ++++++++----- 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index fbbef81..b6e24ba 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -33,10 +33,11 @@ import ( ) type DiscordClient struct { - connector *DiscordConnector - usersFromReady map[string]*discordgo.User - UserLogin *bridgev2.UserLogin - Session *discordgo.Session + connector *DiscordConnector + usersFromReady map[string]*discordgo.User + UserLogin *bridgev2.UserLogin + Session *discordgo.Session + hasBegunSyncing bool } 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 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 // keyed by user ID. cl.usersFromReady = make(map[string]*discordgo.User) @@ -140,7 +124,11 @@ func (cl *DiscordClient) connect(ctx context.Context) error { 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 } @@ -160,6 +148,33 @@ func (d *DiscordClient) LogoutRemote(ctx context.Context) { 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) { for _, dm := range d.Session.State.PrivateChannels { d.UserLogin.Log.Debug().Str("channel_id", dm.ID).Msg("Syncing private channel") diff --git a/pkg/connector/login.go b/pkg/connector/login.go index bce24a8..efea9d1 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -48,13 +48,14 @@ func (d *DiscordConnector) CreateLogin(ctx context.Context, user *bridgev2.User, return nil, fmt.Errorf("unknown login flow ID") } - return &DiscordLogin{User: user}, nil + return &DiscordLogin{connector: d, User: user}, nil } type DiscordLogin struct { - User *bridgev2.User - Token string - Session *discordgo.Session + connector *DiscordConnector + User *bridgev2.User + Token string + Session *discordgo.Session } var _ bridgev2.LoginProcessUserInput = (*DiscordLogin)(nil) @@ -104,7 +105,8 @@ func (dl *DiscordLogin) SubmitUserInput(ctx context.Context, input map[string]st } client := DiscordClient{ - Session: session, + connector: dl.connector, + Session: session, } err = client.connect(ctx) if err != nil { @@ -123,9 +125,14 @@ func (dl *DiscordLogin) SubmitUserInput(ctx context.Context, input map[string]st HeartbeatSession: session.HeartbeatSession, }, }, &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 { login.Client = &client + client.UserLogin = login + + // Only now that we have a UserLogin can we begin syncing. + client.BeginSyncingIfUserLoginPresent(ctx) return nil }, DeleteOnConflict: true,