diff --git a/CHANGELOG.md b/CHANGELOG.md index daf954ff0..58c65794e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - `POST /sessions/{sessionId}/chats/unread` publishes its own `MarkChatUnreadDto` rather than sharing `MarkChatReadDto`. The body is unchanged (`chatId` alone), but a generated client sees the schema under a new name. +- ⚠️ **Breaking (Go and Java clients).** `subscribePresence` takes its own `SubscribePresenceRequest` rather than the shared `MarkChatRequest`, which now serves `markUnread` alone. Swap the type at the call site; the wire body is unchanged. `SubscribePresenceDto` had no contract-gate coverage while one type stood for two routes. ### Fixed diff --git a/scripts/check-contract-shapes.mjs b/scripts/check-contract-shapes.mjs index a52cda7bb..d763da72f 100644 --- a/scripts/check-contract-shapes.mjs +++ b/scripts/check-contract-shapes.mjs @@ -87,6 +87,7 @@ const MAPPINGS = { JoinGroupRequest: 'JoinGroupDto', MarkChatReadRequest: 'MarkChatReadDto', MarkChatRequest: 'MarkChatUnreadDto', + SubscribePresenceRequest: 'SubscribePresenceDto', MessageListResponse: 'MessageListResponseDto', MessageRecord: 'MessageListItemDto', MessageResponse: 'MessageResponseDto', @@ -165,11 +166,11 @@ const MAPPINGS = { * these floors as pairs are added makes the shrink loud. */ const MINIMUM_MAPPED = { - 'sdk/javascript/src/types.ts': 79, + 'sdk/javascript/src/types.ts': 80, 'dashboard/src/services/api.ts': 20, - 'sdk/python/openwa/types.py': 74, - 'sdk/go': 75, - 'sdk/java': 79, + 'sdk/python/openwa/types.py': 75, + 'sdk/go': 76, + 'sdk/java': 80, }; /** Known drift, deliberately not gated yet — each line is a to-adjudicate follow-up. */ @@ -233,6 +234,7 @@ const PYTHON_MAPPING = { JoinGroupRequest: 'JoinGroupDto', MarkChatReadRequest: 'MarkChatReadDto', MarkChatRequest: 'MarkChatUnreadDto', + SubscribePresenceRequest: 'SubscribePresenceDto', MessageListResponse: 'MessageListResponseDto', MessageRecord: 'MessageListItemDto', MessageResponse: 'MessageResponseDto', @@ -311,6 +313,7 @@ const GO_MAPPING = { JoinGroupRequest: 'JoinGroupDto', MarkChatReadRequest: 'MarkChatReadDto', MarkChatRequest: 'MarkChatUnreadDto', + SubscribePresenceRequest: 'SubscribePresenceDto', MessageListResponse: 'MessageListResponseDto', MessageRecord: 'MessageListItemDto', MessageResponse: 'MessageResponseDto', @@ -393,6 +396,7 @@ const JAVA_MAPPING = { JoinGroupRequest: 'JoinGroupDto', MarkChatReadRequest: 'MarkChatReadDto', MarkChatRequest: 'MarkChatUnreadDto', + SubscribePresenceRequest: 'SubscribePresenceDto', MessageListResponse: 'MessageListResponseDto', MessageRecord: 'MessageListItemDto', MessageResponse: 'MessageResponseDto', diff --git a/sdk/go/chats.go b/sdk/go/chats.go index 52f693a1c..3eb3cb66e 100644 --- a/sdk/go/chats.go +++ b/sdk/go/chats.go @@ -24,7 +24,7 @@ func (s *ChatsService) List(ctx context.Context, sessionID string, query *ListCh // The subscription belongs to the connection and does NOT survive a restart or an automatic // reconnect, so re-issue it when the session comes back. Subscribe per chat: WhatsApp emits an // update on every transition, so a broad subscription is a firehose. whatsapp-web.js answers 501. -func (s *ChatsService) SubscribePresence(ctx context.Context, sessionID string, body MarkChatRequest) (*SuccessResult, error) { +func (s *ChatsService) SubscribePresence(ctx context.Context, sessionID string, body SubscribePresenceRequest) (*SuccessResult, error) { var out SuccessResult err := s.client.do(ctx, "POST", "/api/sessions/"+pathEscape(sessionID)+"/presence/subscribe", nil, body, &out) return &out, err diff --git a/sdk/go/routing_test.go b/sdk/go/routing_test.go index 089edaa3a..c57477939 100644 --- a/sdk/go/routing_test.go +++ b/sdk/go/routing_test.go @@ -95,7 +95,7 @@ func TestRouting(t *testing.T) { {"Chats.List", func(c *Client) { c.Chats.List(ctx, "s1", nil) }, "GET", "/api/sessions/s1/chats"}, {"Chats.MarkRead", func(c *Client) { c.Chats.MarkRead(ctx, "s1", MarkChatReadRequest{}) }, "POST", "/api/sessions/s1/chats/read"}, - {"Chats.SubscribePresence", func(c *Client) { c.Chats.SubscribePresence(ctx, "s1", MarkChatRequest{}) }, "POST", "/api/sessions/s1/presence/subscribe"}, + {"Chats.SubscribePresence", func(c *Client) { c.Chats.SubscribePresence(ctx, "s1", SubscribePresenceRequest{}) }, "POST", "/api/sessions/s1/presence/subscribe"}, {"Channels.Create", func(c *Client) { c.Channels.Create(ctx, "s1", CreateChannelRequest{}) }, "POST", "/api/sessions/s1/channels"}, {"Channels.Delete", func(c *Client) { c.Channels.Delete(ctx, "s1", "ch1") }, "POST", "/api/sessions/s1/channels/ch1/delete"}, {"Channels.Mute", func(c *Client) { c.Channels.Mute(ctx, "s1", "ch1", MuteChannelRequest{}) }, "POST", "/api/sessions/s1/channels/ch1/mute"}, diff --git a/sdk/go/types_chat.go b/sdk/go/types_chat.go index f6f24ac50..09a46c65c 100644 --- a/sdk/go/types_chat.go +++ b/sdk/go/types_chat.go @@ -19,11 +19,16 @@ type SetOwnPresenceRequest struct { Available bool `json:"available"` } -// MarkChatRequest marks a chat unread, or subscribes to its presence. +// MarkChatRequest marks a chat unread. type MarkChatRequest struct { ChatID string `json:"chatId"` } +// SubscribePresenceRequest subscribes to a chat's presence. +type SubscribePresenceRequest struct { + ChatID string `json:"chatId"` +} + // MarkChatReadRequest marks a chat read, optionally naming the messages to acknowledge. type MarkChatReadRequest struct { ChatID string `json:"chatId"` diff --git a/sdk/java/src/main/java/com/rmyndharis/openwa/model/MarkChatRequest.java b/sdk/java/src/main/java/com/rmyndharis/openwa/model/MarkChatRequest.java index c11efb5de..193b11c35 100644 --- a/sdk/java/src/main/java/com/rmyndharis/openwa/model/MarkChatRequest.java +++ b/sdk/java/src/main/java/com/rmyndharis/openwa/model/MarkChatRequest.java @@ -1,6 +1,6 @@ package com.rmyndharis.openwa.model; -/** Request body for marking a chat unread, or subscribing to its presence. */ +/** Request body for marking a chat unread. */ public record MarkChatRequest(String chatId) { public static Builder builder() { return new Builder(); diff --git a/sdk/java/src/main/java/com/rmyndharis/openwa/model/SubscribePresenceRequest.java b/sdk/java/src/main/java/com/rmyndharis/openwa/model/SubscribePresenceRequest.java new file mode 100644 index 000000000..e297fecf0 --- /dev/null +++ b/sdk/java/src/main/java/com/rmyndharis/openwa/model/SubscribePresenceRequest.java @@ -0,0 +1,22 @@ +package com.rmyndharis.openwa.model; + +/** Request body for subscribing to a chat's presence. */ +public record SubscribePresenceRequest(String chatId) { + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String chatId; + + /** WhatsApp chat id (JID), e.g. {@code 628123456789@c.us}. */ + public Builder chatId(String v) { + this.chatId = v; + return this; + } + + public SubscribePresenceRequest build() { + return new SubscribePresenceRequest(chatId); + } + } +} diff --git a/sdk/java/src/main/java/com/rmyndharis/openwa/resources/ChatsResource.java b/sdk/java/src/main/java/com/rmyndharis/openwa/resources/ChatsResource.java index 6fd66a25e..36c9157eb 100644 --- a/sdk/java/src/main/java/com/rmyndharis/openwa/resources/ChatsResource.java +++ b/sdk/java/src/main/java/com/rmyndharis/openwa/resources/ChatsResource.java @@ -11,6 +11,7 @@ import com.rmyndharis.openwa.model.ListChatsQuery; import com.rmyndharis.openwa.model.MarkChatReadRequest; import com.rmyndharis.openwa.model.MarkChatRequest; +import com.rmyndharis.openwa.model.SubscribePresenceRequest; import com.rmyndharis.openwa.model.MuteChatRequest; import com.rmyndharis.openwa.model.PinChatRequest; import com.rmyndharis.openwa.model.SendChatStateRequest; @@ -50,7 +51,7 @@ public List list(String sessionId, ListChatsQuery query) { * reconnect, so re-issue it when the session comes back. Subscribe per chat: WhatsApp emits an * update on every transition. whatsapp-web.js answers {@code 501}. */ - public SuccessResult subscribePresence(String sessionId, MarkChatRequest body) { + public SuccessResult subscribePresence(String sessionId, SubscribePresenceRequest body) { return client.request( HttpMethod.POST, "/api/sessions/" + encodeSegment(sessionId) + "/presence/subscribe", diff --git a/sdk/javascript/src/resources/chats.ts b/sdk/javascript/src/resources/chats.ts index 61da4559b..00eaab8d8 100644 --- a/sdk/javascript/src/resources/chats.ts +++ b/sdk/javascript/src/resources/chats.ts @@ -18,6 +18,7 @@ import type { DeleteChatRequest, MarkChatRequest, MarkChatReadRequest, + SubscribePresenceRequest, ChatPresence, SendChatStateRequest, SuccessResult, @@ -48,7 +49,7 @@ export class ChatsResource { * reconnect, so re-issue it when the session comes back. Subscribe per chat: WhatsApp emits an * update on every transition, so a broad subscription is a firehose. whatsapp-web.js answers 501. */ - subscribePresence(sessionId: string, body: MarkChatRequest): Promise { + subscribePresence(sessionId: string, body: SubscribePresenceRequest): Promise { return this.client.request({ method: 'POST', path: `/api/sessions/${encodeSegment(sessionId)}/presence/subscribe`, diff --git a/sdk/javascript/src/types.ts b/sdk/javascript/src/types.ts index a77e31041..2a845881e 100644 --- a/sdk/javascript/src/types.ts +++ b/sdk/javascript/src/types.ts @@ -968,11 +968,16 @@ export interface TransferChannelOwnershipRequest { newOwnerId: Jid; } -/** Body for {@link ChatsResource.markUnread} and {@link ChatsResource.subscribePresence}. */ +/** Body for {@link ChatsResource.markUnread}. */ export interface MarkChatRequest { chatId: Jid; } +/** Body for {@link ChatsResource.subscribePresence}. */ +export interface SubscribePresenceRequest { + chatId: Jid; +} + /** Body for {@link ChatsResource.markRead}. */ export interface MarkChatReadRequest extends MarkChatRequest { /** diff --git a/sdk/python/openwa/resources/chats.py b/sdk/python/openwa/resources/chats.py index 13866069a..bfa131a18 100644 --- a/sdk/python/openwa/resources/chats.py +++ b/sdk/python/openwa/resources/chats.py @@ -18,6 +18,7 @@ DeleteChatRequest, MarkChatReadRequest, MarkChatRequest, + SubscribePresenceRequest, SendChatStateRequest, SuccessResult, ) @@ -38,7 +39,7 @@ def __init__(self, http: "HttpExecutor") -> None: def list(self, session_id: str, query: ListChatsQuery | None = None) -> list[ChatSummary]: return self._http.request("GET", f"/api/sessions/{quote_segment(session_id)}/chats", query=query) - def subscribe_presence(self, session_id: str, body: MarkChatRequest) -> SuccessResult: + def subscribe_presence(self, session_id: str, body: SubscribePresenceRequest) -> SuccessResult: """Subscribe to a chat's presence; updates arrive as presence.update events. Presence cannot be fetched from either engine, only received. The subscription belongs to diff --git a/sdk/python/openwa/types.py b/sdk/python/openwa/types.py index 182c03f97..0ac6401f9 100644 --- a/sdk/python/openwa/types.py +++ b/sdk/python/openwa/types.py @@ -882,7 +882,12 @@ class ChatSummary(TypedDict): class MarkChatRequest(TypedDict): - # Body for mark_unread and subscribe_presence, both of which take the chat id alone. + # Body for mark_unread. + chatId: Jid + + +class SubscribePresenceRequest(TypedDict): + # Body for subscribe_presence. chatId: Jid