diff --git a/commands.go b/commands.go index c1fe1aa..7328c1d 100644 --- a/commands.go +++ b/commands.go @@ -159,10 +159,11 @@ func fnLoginGoogle(ce *WrappedCommandEvent) { } const ( - pairingErrMsgNoDevices = "No devices found. Make sure you've enabled account pairing in the Google Messages app on your phone." - pairingErrMsgIncorrectEmoji = "Incorrect emoji chosen on phone, please try again" - pairingErrMsgCancelled = "Pairing cancelled on phone" - pairingErrMsgTimeout = "Pairing timed out, please try again" + pairingErrMsgNoDevices = "No devices found. Make sure you've enabled account pairing in the Google Messages app on your phone." + pairingErrPhoneNotResponding = "Phone not responding. Make sure your phone is connected to the internet and that account pairing is enabled in the Google Messages app." + pairingErrMsgIncorrectEmoji = "Incorrect emoji chosen on phone, please try again" + pairingErrMsgCancelled = "Pairing cancelled on phone" + pairingErrMsgTimeout = "Pairing timed out, please try again" ) func fnLoginGoogleCookies(ce *WrappedCommandEvent) { @@ -194,6 +195,8 @@ func fnLoginGoogleCookies(ce *WrappedCommandEvent) { if err != nil { if errors.Is(err, libgm.ErrNoDevicesFound) { ce.Reply(pairingErrMsgNoDevices) + } else if errors.Is(err, libgm.ErrPairingInitTimeout) { + ce.Reply(pairingErrPhoneNotResponding) } else if errors.Is(err, libgm.ErrIncorrectEmoji) { ce.Reply(pairingErrMsgIncorrectEmoji) } else if errors.Is(err, libgm.ErrPairingCancelled) { diff --git a/libgm/pair_google.go b/libgm/pair_google.go index 65bfbd2..ff032cf 100644 --- a/libgm/pair_google.go +++ b/libgm/pair_google.go @@ -33,6 +33,7 @@ import ( "time" "github.com/google/uuid" + "github.com/rs/zerolog" "go.mau.fi/util/random" "golang.org/x/crypto/hkdf" "google.golang.org/protobuf/proto" @@ -237,13 +238,16 @@ func (ps *PairingSession) ProcessServerInit(msg *gmproto.GaiaPairingResponseCont } var ( - ErrNoCookies = errors.New("gaia pairing requires cookies") - ErrNoDevicesFound = errors.New("no devices found for gaia pairing") - ErrIncorrectEmoji = errors.New("user chose incorrect emoji on phone") - ErrPairingCancelled = errors.New("user cancelled pairing on phone") - ErrPairingTimeout = errors.New("pairing timed out") + ErrNoCookies = errors.New("gaia pairing requires cookies") + ErrNoDevicesFound = errors.New("no devices found for gaia pairing") + ErrIncorrectEmoji = errors.New("user chose incorrect emoji on phone") + ErrPairingCancelled = errors.New("user cancelled pairing on phone") + ErrPairingTimeout = errors.New("pairing timed out") + ErrPairingInitTimeout = errors.New("client init timed out") ) +const GaiaInitTimeout = 15 * time.Second + func (c *Client) DoGaiaPairing(ctx context.Context, emojiCallback func(string)) error { if len(c.AuthData.Cookies) == 0 { return ErrNoCookies @@ -277,12 +281,25 @@ func (c *Client) DoGaiaPairing(ctx context.Context, emojiCallback func(string)) if err != nil { return fmt.Errorf("failed to prepare pairing payloads: %w", err) } - serverInit, err := c.sendGaiaPairingMessage(ctx, ps, gmproto.ActionType_CREATE_GAIA_PAIRING_CLIENT_INIT, clientInit) + initCtx, cancel := context.WithTimeout(ctx, GaiaInitTimeout) + serverInit, err := c.sendGaiaPairingMessage(initCtx, ps, gmproto.ActionType_CREATE_GAIA_PAIRING_CLIENT_INIT, clientInit) + cancel() if err != nil { + cancelErr := c.cancelGaiaPairing(ps) + if cancelErr != nil { + zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to send gaia pairing cancel request after init timeout") + } + if errors.Is(err, context.DeadlineExceeded) { + return ErrPairingInitTimeout + } return fmt.Errorf("failed to send client init: %w", err) } pairingEmoji, err := ps.ProcessServerInit(serverInit) if err != nil { + cancelErr := c.cancelGaiaPairing(ps) + if cancelErr != nil { + zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to send gaia pairing cancel request after error processing server init") + } return fmt.Errorf("error processing server init: %w", err) } emojiCallback(pairingEmoji) @@ -317,6 +334,16 @@ func (c *Client) DoGaiaPairing(ctx context.Context, emojiCallback func(string)) return nil } +func (c *Client) cancelGaiaPairing(sess PairingSession) error { + return c.sessionHandler.sendMessageNoResponse(SendMessageParams{ + Action: gmproto.ActionType_CANCEL_GAIA_PAIRING, + RequestID: sess.UUID.String(), + DontEncrypt: true, + CustomTTL: (300 * time.Second).Microseconds(), + MessageType: gmproto.MessageType_GAIA_2, + }) +} + func (c *Client) sendGaiaPairingMessage(ctx context.Context, sess PairingSession, action gmproto.ActionType, msg []byte) (*gmproto.GaiaPairingResponseContainer, error) { respCh, err := c.sessionHandler.sendAsyncMessage(SendMessageParams{ Action: action, diff --git a/provisioning.go b/provisioning.go index ad3595e..7085bdb 100644 --- a/provisioning.go +++ b/provisioning.go @@ -356,6 +356,11 @@ func (prov *ProvisioningAPI) GoogleLoginStart(w http.ResponseWriter, r *http.Req Error: pairingErrMsgNoDevices, ErrCode: "no-devices-found", }) + case errors.Is(err, libgm.ErrPairingInitTimeout): + jsonResponse(w, http.StatusBadRequest, Error{ + Error: pairingErrPhoneNotResponding, + ErrCode: "timeout", + }) default: jsonResponse(w, http.StatusInternalServerError, Error{ Error: "Failed to start login",