From 7bbbebd7d5c4eeb699d243e4351b7b6179c530bd Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Tue, 14 Jul 2026 11:05:23 +0100 Subject: [PATCH] messagix/bloks: implement timer cancel function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on bloks debug payload, we see the timer get cancelled and immediately restarted, without this change we error at the cancel. Bloks debug payload: ``` TakeLast( If( is_timer_running AND approval_id != "XXX", ← this page's approval id { timer.Cancel(GetArg 0, "approve_from_another_device_polling_timer") is_timer_running := false }, ← kill a STALE timer null ), If( is_timer_running, null, ← already running: no-op { is_timer_running := true timer.Start(GetArg 0, 2000, true, ← restart: poll every 2000ms λ: If(is_loading, null, invoke "com.bloks.www.two_step_verification.afad_state.async" ...), "approve_from_another_device_polling_timer") } ) ) ``` --- pkg/messagix/bloks/cmd/main.go | 4 ++++ pkg/messagix/bloks/interp.go | 17 +++++++++++++++++ pkg/messagix/bloks/selenium.go | 19 +++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/pkg/messagix/bloks/cmd/main.go b/pkg/messagix/bloks/cmd/main.go index 58a99490..98560185 100644 --- a/pkg/messagix/bloks/cmd/main.go +++ b/pkg/messagix/bloks/cmd/main.go @@ -212,6 +212,10 @@ func mainE() error { } return nil }, + CancelTimer: func(name string) error { + fmt.Printf("timer cancelled: %s\n", name) + return nil + }, OpenURL: func(url string) error { if lastURL != "" { return fmt.Errorf("already opened a url this session") diff --git a/pkg/messagix/bloks/interp.go b/pkg/messagix/bloks/interp.go index d40673db..ce8c2496 100644 --- a/pkg/messagix/bloks/interp.go +++ b/pkg/messagix/bloks/interp.go @@ -33,6 +33,7 @@ type InterpBridge struct { DisplayNewScreen func(context.Context, string, *BloksBundle) error HandleLoginResponse func(ctx context.Context, data string) error StartTimer func(name string, interval time.Duration, callback func() error) error + CancelTimer func(name string) error OpenURL func(url string) error HandleVariableChange func(ctx context.Context, name string, value *BloksScriptLiteral) error } @@ -154,6 +155,11 @@ func NewInterpreter(ctx context.Context, b *BloksBundle, br *InterpBridge, old * return fmt.Errorf("unhandled timer %s", name) } } + if br.CancelTimer == nil { + br.CancelTimer = func(name string) error { + return fmt.Errorf("unhandled timer cancel %s", name) + } + } if br.OpenURL == nil { br.OpenURL = func(url string) error { return fmt.Errorf("unhandled url %s", url) @@ -1095,6 +1101,17 @@ func (i *Interpreter) Evaluate(ctx context.Context, form *BloksScriptNode) (*Blo return nil, err } return BloksNothing, nil + case "bk.action.timer.Cancel": + // Args are (timer context, name), the first of which we have no use for. + name, err := evalAs[string](ctx, i, &call.Args[1], "timer.cancel") + if err != nil { + return nil, err + } + err = i.Bridge.CancelTimer(name) + if err != nil { + return nil, err + } + return BloksNothing, nil case "bk.action.caa.PresentCheckpointsFlow": flowB, err := evalAs[string](ctx, i, &call.Args[0], "presentcheckpointsflow") if err != nil { diff --git a/pkg/messagix/bloks/selenium.go b/pkg/messagix/bloks/selenium.go index f22dfaa9..998b1a65 100644 --- a/pkg/messagix/bloks/selenium.go +++ b/pkg/messagix/bloks/selenium.go @@ -24,6 +24,7 @@ import ( var ( ErrLoginPhoneNumber = bridgev2.RespError{ErrCode: "FI.MAU.META_PHONE_NUMBER", Err: "Phone number login is not supported, please try email address or username", StatusCode: http.StatusBadRequest} ErrLoginInvalidUsername = bridgev2.RespError{ErrCode: "FI.MAU.META_MATRIX_ID", Err: "That doesn't look like a valid username, please enter your Facebook email address or username", StatusCode: http.StatusBadRequest} + ErrLoginAFADStopped = bridgev2.RespError{ErrCode: "FI.MAU.META_AFAD_STOPPED", Err: "The approval request expired or was denied, please try logging in again", StatusCode: http.StatusBadRequest} ) // This error is returned in cases where we have observed Meta returning an error that is @@ -528,6 +529,8 @@ func NewBrowser(cfg *BrowserConfig) *Browser { newState = StateBackupCodePage case "com.bloks.www.ap.two_step_verification.contactpoint_chooser": newState = StateChooseNumberPage + case "com.bloks.www.approve_from_another_device.xmds.challenged_device_denied": + return ErrLoginAFADStopped case "com.bloks.www.two_step_verification.enter_whatsapp_code": newState = StateWhatsAppPage case "com.bloks.www.ap.passkey_auth": @@ -565,6 +568,16 @@ func NewBrowser(cfg *BrowserConfig) *Browser { } return nil }, + CancelTimer: func(name string) error { + switch name { + case "approve_from_another_device_polling_timer": + b.AFADInterval = 0 + b.AFADCallback = nil + default: + return fmt.Errorf("unexpected timer cancel %s", name) + } + return nil + }, OpenURL: func(url string) error { b.DisplayedURL = url return nil @@ -1261,9 +1274,15 @@ func (b *Browser) DoLoginStep(ctx context.Context, userInput map[string]string) case StateAFADPageWaiting: for b.State == StateAFADPageWaiting { + if b.AFADCallback == nil { + return nil, ErrLoginAFADStopped + } time.Sleep(b.AFADInterval) err := b.AFADCallback() if err != nil { + if ctxErr := ctx.Err(); ctxErr != nil { + return nil, fmt.Errorf("login cancelled while waiting for approval: %w", ctxErr) + } return nil, fmt.Errorf("AFAD callback: %w", err) } }