From a634395300361cf64cf6f22453dd6339f442eba2 Mon Sep 17 00:00:00 2001 From: Nicolas Novis Date: Fri, 24 Jul 2026 18:53:11 -0300 Subject: [PATCH 1/3] fix(chat): send on-demand history-sync request to self via SendPeerMessage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The on-demand history-sync request was sent to messageInfo.Chat (the contact) with Peer:true. Peer messages must go to the account's OWN JID — sending to the contact fails with "no signal session established" and never returns history. whatsmeow's BuildHistorySyncRequest documents SendPeerMessage as the way to send it (it targets getOwnID().ToNonAD() with Peer:true). The target chat/cursor is already encoded inside the built request, so no information is lost. --- pkg/chat/service/chat_service.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/chat/service/chat_service.go b/pkg/chat/service/chat_service.go index c4eda9d7..ae641789 100644 --- a/pkg/chat/service/chat_service.go +++ b/pkg/chat/service/chat_service.go @@ -234,7 +234,12 @@ func (c *chatService) HistorySyncRequest(data *HistorySyncRequestStruct, instanc histRequest := client.BuildHistorySyncRequest(&messageInfo, data.Count) - res, err := client.SendMessage(context.Background(), messageInfo.Chat, histRequest, whatsmeow.SendRequestExtra{Peer: true}) + // On-demand history-sync requests must be sent to our OWN JID as a peer message, not to the + // contact. SendPeerMessage does exactly that (getOwnID().ToNonAD() + Peer:true). Sending it to + // messageInfo.Chat (the contact) fails with "no signal session established" and never returns + // history. whatsmeow's BuildHistorySyncRequest doc: "The built message can be sent using + // Client.SendPeerMessage." The target chat/cursor is already encoded inside histRequest. + res, err := client.SendPeerMessage(context.Background(), histRequest) if err != nil { c.loggerWrapper.GetLogger(instance.Id).LogError("[%s] error history sync request: %v", instance.Id, err) return nil, err From 28b84f39966b0e84b5cf04ba0f11840200c531c3 Mon Sep 17 00:00:00 2001 From: Nicolas Novis Date: Fri, 24 Jul 2026 18:53:11 -0300 Subject: [PATCH 2/3] feat(whatsmeow): set HistorySyncConfig for a deep on-link history backfill RequireFullSync=true alone yields a shallow/uneven backfill because the phone falls back to conservative defaults when no HistorySyncConfig is provided. Setting an explicit deep window makes newly linked devices receive the full available message history. --- pkg/whatsmeow/service/whatsmeow.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/whatsmeow/service/whatsmeow.go b/pkg/whatsmeow/service/whatsmeow.go index 366f0edb..e4c8a8cc 100644 --- a/pkg/whatsmeow/service/whatsmeow.go +++ b/pkg/whatsmeow/service/whatsmeow.go @@ -374,6 +374,14 @@ func (w whatsmeowService) StartClient(cd *ClientData) { store.DeviceProps.Os = &cd.Instance.OsName store.DeviceProps.RequireFullSync = proto.Bool(true) + // RequireFullSync alone still yields a shallow/uneven backfill because the phone falls back to + // conservative defaults. Explicitly request a deep on-link HistorySync window so newly linked + // devices receive the full available message history. + store.DeviceProps.HistorySyncConfig = &waCompanionReg.DeviceProps_HistorySyncConfig{ + FullSyncDaysLimit: proto.Uint32(3650), + FullSyncSizeMbLimit: proto.Uint32(2048), + StorageQuotaMb: proto.Uint32(2048), + } if w.config.WhatsappVersionMajor != 0 && w.config.WhatsappVersionMinor != 0 && w.config.WhatsappVersionPatch != 0 { w.loggerWrapper.GetLogger(cd.Instance.Id).LogInfo("[%s] Setting whatsapp version to %d.%d.%d", cd.Instance.Id, w.config.WhatsappVersionMajor, w.config.WhatsappVersionMinor, w.config.WhatsappVersionPatch) From ff3542b33a6167de9b269aa13203fe92b4aac907 Mon Sep 17 00:00:00 2001 From: Nicolas Novis Date: Fri, 24 Jul 2026 20:25:14 -0300 Subject: [PATCH 3/3] =?UTF-8?q?refactor(history):=20address=20review=20?= =?UTF-8?q?=E2=80=94=20named=20HistorySyncConfig=20constants=20+=20thread?= =?UTF-8?q?=20caller=20context?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extract the HistorySyncConfig depth/size limits into named package constants (historyFullSyncDaysLimit / historyFullSyncSizeMbLimit / historyStorageQuotaMb) so deployments can tune them in one place. - Thread the caller's context into HistorySyncRequest instead of a detached context.Background(), so the on-demand history-sync peer message can be cancelled / time-bounded by the request. Signature updated across the ChatService interface, the implementation, and the gin handler (ctx.Request.Context()). --- pkg/chat/handler/chat_handler.go | 2 +- pkg/chat/service/chat_service.go | 7 ++++--- pkg/whatsmeow/service/whatsmeow.go | 18 ++++++++++++++---- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/pkg/chat/handler/chat_handler.go b/pkg/chat/handler/chat_handler.go index c89f2936..35adc642 100644 --- a/pkg/chat/handler/chat_handler.go +++ b/pkg/chat/handler/chat_handler.go @@ -319,7 +319,7 @@ func (c *chatHandler) HistorySyncRequest(ctx *gin.Context) { return } - resp, err := c.chatService.HistorySyncRequest(data, instance) + resp, err := c.chatService.HistorySyncRequest(ctx.Request.Context(), data, instance) if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return diff --git a/pkg/chat/service/chat_service.go b/pkg/chat/service/chat_service.go index ae641789..0a6f28ca 100644 --- a/pkg/chat/service/chat_service.go +++ b/pkg/chat/service/chat_service.go @@ -21,7 +21,7 @@ type ChatService interface { ChatUnarchive(data *BodyStruct, instance *instance_model.Instance) (string, error) ChatMute(data *BodyStruct, instance *instance_model.Instance) (string, error) ChatUnmute(data *BodyStruct, instance *instance_model.Instance) (string, error) - HistorySyncRequest(data *HistorySyncRequestStruct, instance *instance_model.Instance) (*whatsmeow.SendResponse, error) + HistorySyncRequest(ctx context.Context, data *HistorySyncRequestStruct, instance *instance_model.Instance) (*whatsmeow.SendResponse, error) } type chatService struct { @@ -216,7 +216,7 @@ func (c *chatService) ChatUnmute(data *BodyStruct, instance *instance_model.Inst return ts.String(), nil } -func (c *chatService) HistorySyncRequest(data *HistorySyncRequestStruct, instance *instance_model.Instance) (*whatsmeow.SendResponse, error) { +func (c *chatService) HistorySyncRequest(ctx context.Context, data *HistorySyncRequestStruct, instance *instance_model.Instance) (*whatsmeow.SendResponse, error) { client, err := c.ensureClientConnected(instance.Id) if err != nil { return nil, err @@ -239,7 +239,8 @@ func (c *chatService) HistorySyncRequest(data *HistorySyncRequestStruct, instanc // messageInfo.Chat (the contact) fails with "no signal session established" and never returns // history. whatsmeow's BuildHistorySyncRequest doc: "The built message can be sent using // Client.SendPeerMessage." The target chat/cursor is already encoded inside histRequest. - res, err := client.SendPeerMessage(context.Background(), histRequest) + // Uses the caller's context so the request can be cancelled / time-bounded. + res, err := client.SendPeerMessage(ctx, histRequest) if err != nil { c.loggerWrapper.GetLogger(instance.Id).LogError("[%s] error history sync request: %v", instance.Id, err) return nil, err diff --git a/pkg/whatsmeow/service/whatsmeow.go b/pkg/whatsmeow/service/whatsmeow.go index e4c8a8cc..63ca20c6 100644 --- a/pkg/whatsmeow/service/whatsmeow.go +++ b/pkg/whatsmeow/service/whatsmeow.go @@ -301,6 +301,15 @@ func (w whatsmeowService) ForceUpdateJid(instanceId string, number string) error return nil } +// History-sync depth requested from the phone when a device links (DeviceProps.HistorySyncConfig). +// Generous defaults so a newly linked device receives the full available history; tune here if +// bandwidth/storage is a concern. +const ( + historyFullSyncDaysLimit = 3650 // ~10 years + historyFullSyncSizeMbLimit = 2048 // 2 GB + historyStorageQuotaMb = 2048 // 2 GB +) + func (w whatsmeowService) StartClient(cd *ClientData) { w.loggerWrapper.GetLogger(cd.Instance.Id).LogInfo("Starting websocket connection to Whatsapp for user '%s'", cd.Instance.Id) @@ -376,11 +385,12 @@ func (w whatsmeowService) StartClient(cd *ClientData) { store.DeviceProps.RequireFullSync = proto.Bool(true) // RequireFullSync alone still yields a shallow/uneven backfill because the phone falls back to // conservative defaults. Explicitly request a deep on-link HistorySync window so newly linked - // devices receive the full available message history. + // devices receive the full available message history. Values are named constants so deployments + // can tune history depth / resource usage in one place. store.DeviceProps.HistorySyncConfig = &waCompanionReg.DeviceProps_HistorySyncConfig{ - FullSyncDaysLimit: proto.Uint32(3650), - FullSyncSizeMbLimit: proto.Uint32(2048), - StorageQuotaMb: proto.Uint32(2048), + FullSyncDaysLimit: proto.Uint32(historyFullSyncDaysLimit), + FullSyncSizeMbLimit: proto.Uint32(historyFullSyncSizeMbLimit), + StorageQuotaMb: proto.Uint32(historyStorageQuotaMb), } if w.config.WhatsappVersionMajor != 0 && w.config.WhatsappVersionMinor != 0 && w.config.WhatsappVersionPatch != 0 {