Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 35 additions & 5 deletions whatsrust/lib/client_lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"context"
"errors"
"os"
"sync"
"time"
"unsafe"

Expand All @@ -27,13 +28,39 @@ import (
"google.golang.org/protobuf/proto"
)

// client and qrChan remain package-owned because the other bridge actions use
// the same client after this lifecycle boundary is crossed.
// client remains package-owned because the other bridge actions use the same
// client after this lifecycle boundary is crossed. The QR stream and handler
// registration state live here so reconnect setup has one resettable owner.
var client *whatsmeow.Client
var qrChan <-chan whatsmeow.QRChannelItem

type clientLifecycleState struct {
mu sync.Mutex
qrChan <-chan whatsmeow.QRChannelItem
handlersRegistered bool
}

var lifecycleState clientLifecycleState

func (state *clientLifecycleState) reset() {
state.mu.Lock()
defer state.mu.Unlock()
state.qrChan = nil
state.handlersRegistered = false
}

func (state *clientLifecycleState) registerEventHandlers(register func()) {
state.mu.Lock()
defer state.mu.Unlock()
if state.handlersRegistered {
return
}
register()
state.handlersRegistered = true
}

//export C_NewClient
func C_NewClient(dbPath *C.char) {
lifecycleState.reset()
clearAuthenticatedPushNameCache()
rawPresenceProbe.reset(os.Getenv("WPTUI_PRESENCE_DEBUG") == "1")
requestFullHistorySync()
Expand Down Expand Up @@ -68,12 +95,15 @@ func C_Connect(handler C.QrCallback, data unsafe.Pointer) {
store.DeviceProps.GetHistorySyncConfig().GetFullSyncSizeMbLimit(),
store.DeviceProps.GetPlatformType(),
)
qrChan, _ = client.GetQRChannel(context.Background())
lifecycleState.mu.Lock()
lifecycleState.qrChan, _ = client.GetQRChannel(context.Background())
qrChannel := lifecycleState.qrChan
lifecycleState.mu.Unlock()
if err := client.Connect(); err != nil {
panic(err)
}

for evt := range qrChan {
for evt := range qrChannel {
if evt.Event != "code" {
continue
}
Expand Down
32 changes: 32 additions & 0 deletions whatsrust/lib/client_lifecycle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,40 @@ package main
import (
"errors"
"testing"

"go.mau.fi/whatsmeow"
)

func TestClientLifecycleRegistrationIsIdempotent(t *testing.T) {
var state clientLifecycleState
registrations := 0
register := func() { registrations++ }

state.registerEventHandlers(register)
state.registerEventHandlers(register)

if registrations != 1 {
t.Fatalf("registrations = %d, want one", registrations)
}
}

func TestClientLifecycleResetClearsRegistrationAndQR(t *testing.T) {
qr := make(chan whatsmeow.QRChannelItem)
state := clientLifecycleState{
qrChan: qr,
handlersRegistered: true,
}

state.reset()

if state.qrChan != nil {
t.Fatal("reset retained QR channel")
}
if state.handlersRegistered {
t.Fatal("reset retained handler registration")
}
}

func TestLogoutStatusAfterRemoteFailure(t *testing.T) {
for _, testCase := range []struct {
name string
Expand Down
6 changes: 6 additions & 0 deletions whatsrust/lib/event_wiring.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ func (dispatcher *viewOnceUnavailableDispatcher) dispatchOnce(info types.Message
}

func AddEventHandlers() {
lifecycleState.registerEventHandlers(func() {
addEventHandlers()
})
}

func addEventHandlers() {
viewOnceDispatcher := newViewOnceUnavailableDispatcher(HandleViewOnceUnavailableMessage)
client.AddEventHandler(func(rawEvt any) {
messageActionCensusDiagnostic(rawEvt)
Expand Down
Loading