From 9e4d805cd96a9fb328dd322eb3b8621f95d34943 Mon Sep 17 00:00:00 2001 From: James Miller Date: Mon, 18 May 2026 12:48:11 -0700 Subject: [PATCH 01/34] release v1.13.1 changelog, documentation, and blog post --- blog/2026-05-18-new-in-v1.13.mdx | 140 +++++++++++++++++++ docs/connections/claims.mdx | 69 ++++++++- docs/connections/client-http-api.mdx | 104 +++++++++++++- docs/connections/keep-alive.mdx | 13 +- docs/installation/changelog.mdx | 19 +++ docs/server-api/events.mdx | 200 +++++++++++++++++++++++++++ 6 files changed, 532 insertions(+), 13 deletions(-) create mode 100644 blog/2026-05-18-new-in-v1.13.mdx diff --git a/blog/2026-05-18-new-in-v1.13.mdx b/blog/2026-05-18-new-in-v1.13.mdx new file mode 100644 index 0000000..76b087f --- /dev/null +++ b/blog/2026-05-18-new-in-v1.13.mdx @@ -0,0 +1,140 @@ +--- +slug: message-reactions-and-presence-pubsub +title: Message reactions, presence pub/sub, and heartbeat enforcement +authors: [james] +tags: [releases, features, channels, presence] +description: Hotsock v1.13 adds message reactions with per-event permissions and real-time delivery, presence member events to SNS/EventBridge, and a heartbeatTimeout claim for server-enforced connection liveness. +--- + +Hotsock v1.13 introduces **message reactions**, **presence member events on SNS/EventBridge**, and a **`heartbeatTimeout` claim** for server-enforced connection liveness. The release also adds an `expandReactions` flag to `listMessages` so you can fetch reaction details with user attribution in a single call. + +{/* truncate */} + +### Message reactions {/* #message-reactions */} + +Clients can now add and remove reactions on stored messages, with real-time delivery to all channel subscribers, aggregated counts in message history, and a separate endpoint to list who reacted with what. + +Reactions are gated by a new [`react`](/docs/connections/claims/#channels.messages.react) directive on channel message event patterns, alongside the existing `publish`, `store`, `echo`, and other directives. A connection needs three things to react: an active channel subscription, a [`uid`](/docs/connections/claims/#uid), and a stored target message that hasn't expired. + +#### Setting up permissions {/* #setting-up-permissions */} + +```json +{ + "exp": 1747574400, + "scope": "connect", + "uid": "Jim", + "channels": { + "room.123": { + "subscribe": true, + "messages": { + "chat": { + "publish": true, + "echo": true, + "store": 31536000, + // highlight-next-line + "react": true + } + } + } + } +} +``` + +#### Adding and removing reactions {/* #adding-and-removing-reactions */} + +Clients send a `hotsock.messageReaction` message on the WebSocket with the target message ID, the reaction value, and an explicit `add` or `remove` action: + +``` +> {"event":"hotsock.messageReaction", "channel":"room.123", "data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW", "reaction":"thumbsup", "action":"add"}} +``` + +All channel subscribers immediately receive a `hotsock.messageReactionAdded` event: + +``` +< {"id":"01JB4K7M2N...","event":"hotsock.messageReactionAdded","channel":"room.123","data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW","reaction":"thumbsup","action":"add"},"meta":{"uid":"Jim","umd":null}} +``` + +Reaction data can be emoji characters, Slack-style colon-wrapped strings like `:thumbsup:`, or any short string up to 100 bytes. Each user can have one reaction per reaction value per message — adding the same reaction again is a no-op. + +#### Reactions in message history {/* #reactions-in-message-history */} + +[`listMessages`](/docs/connections/client-http-api/#connection/listMessages) responses now include a `reactions` object on each message that has reactions, keyed by reaction value: + +```json +{ + "messages": [ + { + "id": "01JA3S0P7FB7WVYS67X316M32S", + "event": "chat", + "channel": "room.123", + "data": "Wow. Can we make it a different moment?", + "meta": { "uid": "Jim", "umd": null }, + // highlight-next-line + "reactions": { "thumbsup": { "count": 2 }, "heart": { "count": 1 } } + } + ] +} +``` + +Counts are returned for free — they come from a metadata item adjacent to each message that gets fetched by the same query that loads messages. Pass [`expandReactions: true`](/docs/connections/client-http-api/#connection/listMessages.expandReactions) to also include an `items` array per reaction with the user behind each one: + +```json +"reactions": { + "thumbsup": { + "count": 2, + "items": [ + { "userId": "Jim", "userMetadata": { "name": "Jim Halpert" } }, + { "userId": "Pam" } + ] + } +} +``` + +You can also fetch reactions for a single message with the dedicated [`connection/listReactions`](/docs/connections/client-http-api/#connection/listReactions) endpoint. + +#### Pub/sub events for reactions {/* #pubsub-events-for-reactions */} + +[`hotsock.messageReactionAdded`](/docs/server-api/events/#hotsock.messageReactionAdded) and [`hotsock.messageReactionRemoved`](/docs/server-api/events/#hotsock.messageReactionRemoved) events are always emitted to SNS/EventBridge when reactions are added or removed — no `emitPubSubEvent` directive needed. This makes server-side aggregation, moderation, or analytics straightforward. + +### Presence member events on SNS/EventBridge {/* #presence-member-pubsub */} + +Presence channel member lifecycle events ([`hotsock.memberAdded`](/docs/server-api/events/#hotsock.memberAdded), [`hotsock.memberRemoved`](/docs/server-api/events/#hotsock.memberRemoved), [`hotsock.memberUpdated`](/docs/server-api/events/#hotsock.memberUpdated)) are now also emitted to SNS/EventBridge, gated by the existing `PublishEventsToSNSParameter` / `PublishEventsToEventBridgeParameter` settings. The pub/sub events are deduplicated by `uid` to match WebSocket fan-out semantics — joining from a second device when you're already present doesn't trigger another `memberAdded`. + +The pub/sub `data` payload uses the same single-entity shape as the rest of the public event types, with `uid`, `umd`, and `members` at the root: + +```json +{ + "uid": "Dwight", + "umd": { "status": "available" }, + "members": [ + { "uid": "Jim", "umd": null }, + { "uid": "Dwight", "umd": { "status": "available" } } + ] +} +``` + +`dataType` is `channelMember`, so SNS filter policies and EventBridge rules can target presence events specifically. + +### Server-enforced heartbeats {/* #server-enforced-heartbeats */} + +The new [`heartbeatTimeout`](/docs/connections/claims/#heartbeatTimeout) connect-token claim sets a maximum interval between client-initiated `hotsock.heartbeat` messages. If the server doesn't see a heartbeat within that window, the connection is forcefully disconnected: + +```json +{ + "scope": "connect", + // highlight-next-line + "heartbeatTimeout": 30 +} +``` + +Valid values are 5–600 seconds. This is most useful on [presence channels](/docs/channels/presence/) where accurate detection of dropped clients matters more than waiting for API Gateway's 10-minute idle timeout. The client just needs to send `{"event":"hotsock.heartbeat"}` periodically — the server records receipt and reschedules the next check relative to the last heartbeat, with no reply. + +### Other improvements {/* #other-improvements */} + +- Faster subscription cleanup when many connections disconnect or unsubscribe at the same time. +- Tighter heartbeat-check scheduling so stale connections are disconnected closer to their actual deadline. +- Updated to Go 1.26.3 and the [latest aws-sdk-go-v2 modules](https://github.com/aws/aws-sdk-go-v2/releases/tag/release-2026-05-12). + +### Wrapping up {/* #wrapping-up */} + +Existing installations with auto-update enabled are already running v1.13.1 and have access to these features today. Other installations can be [manually updated](/docs/installation/updates/#manually-update-installation) at any time. A [full changelog](/docs/installation/changelog/#v1.13.1) is available with the complete list of changes included in this release. diff --git a/docs/connections/claims.mdx b/docs/connections/claims.mdx index b9c791c..e8e6f77 100644 --- a/docs/connections/claims.mdx +++ b/docs/connections/claims.mdx @@ -226,7 +226,7 @@ If you wanted to allow access to the entire message history of a channel without `Object` (optional) - Manages the permissions and directives for client-initiated messages that are published directly to the WebSocket on the channel(s) for the specified events. Each object key is the name of an event and can include asterisks (\*) anywhere in the string to denote wildcards. Keys can also use the `#regex:` prefix to specify a regular expression pattern for matching event names. Each object value is another object with the settings for that event or event pattern. -Each object inside the messages object accepts [`broadcast`](#channels.messages.broadcast), [`echo`](#channels.messages.echo), [`emitPubSubEvent`](#channels.messages.emitPubSubEvent), [`publish`](#channels.messages.publish), [`scheduleBefore`](#channels.messages.scheduleBefore), and [`store`](#channels.messages.store) attributes. +Each object inside the messages object accepts [`broadcast`](#channels.messages.broadcast), [`echo`](#channels.messages.echo), [`emitPubSubEvent`](#channels.messages.emitPubSubEvent), [`publish`](#channels.messages.publish), [`react`](#channels.messages.react), [`scheduleBefore`](#channels.messages.scheduleBefore), and [`store`](#channels.messages.store) attributes. #### Regex patterns {/* #channels.messages--regex */} @@ -405,6 +405,59 @@ In the following example, since `chat.*` includes `chat.admin`, this connection ::: +#### `react` {/* #channels.messages.react */} + +`Boolean` (optional) - Controls whether this connection can add or remove reactions on stored messages that match this event pattern. Reactions require an active channel subscription and a [`uid`](#uid) on the connection. Default is `false`. + +When a reaction is added or removed, all channel subscribers receive a `hotsock.messageReactionAdded` or `hotsock.messageReactionRemoved` message in real-time. Reaction counts are also included in [`listMessages`](./client-http-api.mdx#connection/listMessages) responses, and individual reactions can be listed with [`listReactions`](./client-http-api.mdx#connection/listReactions). + +The following allows reactions on `chat` messages in the "mychannel" channel. + +```json +{ + "channels": { + "mychannel": { + "subscribe": true, + "messages": { + "chat": { + "publish": true, + "echo": true, + "store": 31536000, + // highlight-next-line + "react": true + } + } + } + } +} +``` + +To add a reaction, send a `hotsock.messageReaction` message on the WebSocket: + +``` +> {"event":"hotsock.messageReaction", "channel":"mychannel", "data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW", "reaction":"thumbsup", "action":"add"}} +``` + +To remove a reaction: + +``` +> {"event":"hotsock.messageReaction", "channel":"mychannel", "data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW", "reaction":"thumbsup", "action":"remove"}} +``` + +All channel subscribers receive the reaction event: + +``` +< {"id":"01JB4K7M2N...","event":"hotsock.messageReactionAdded","channel":"mychannel","data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW","reaction":"thumbsup","action":"add"},"meta":{"uid":"Jim","umd":null}} +``` + +Reaction data must not be blank, must be no more than 100 bytes, and must not contain number signs (`#`). Reactions can be emoji characters, Slack-style colon-wrapped strings like `:thumbsup:`, or any other short string. + +:::info +Reactions can only be added to stored messages (messages published with a [`store`](#channels.messages.store) TTL). The target message must exist and not be expired. Each user can have one reaction per reaction value per message — adding the same reaction again is a no-op. +::: + +Also available via the Client HTTP API at [`connection/publishMessage`](./client-http-api.mdx#connection/publishMessage) using `"event": "hotsock.messageReaction"` with the reaction payload as `data`, or via the server-side [Lambda](../server-api/publish-messages.mdx#publish-with-lambda) and [HTTP URL](../server-api/publish-messages.mdx#publish-with-http-url) publish APIs. + #### `scheduleBefore` {/* #channels.messages.scheduleBefore */} `NumericDate` (optional) - If [client-initiated message publishing](#channels.messages.publish) is permitted for this channel and event, the `scheduleBefore` attribute specifies the furthest out future time that this connection can schedule messages for delivery. If supplied, the schedule before time must be expressed as a Unix timestamp - the number of seconds since the Unix epoch. By default, or if `scheduleBefore` is provided as `0` or any timestamp in the past, scheduled message publishing is not permitted. @@ -747,6 +800,20 @@ You can also explicitly set `subscribe` to `false`, preventing subscriptions to } ``` +## `heartbeatTimeout` {/* #heartbeatTimeout */} + +`Integer` (optional) - The maximum number of seconds between client-initiated heartbeats. If set, the connection must send a [`hotsock.heartbeat`](./keep-alive.mdx#send-hotsockheartbeat) event at least this often or it will be forcefully disconnected by the server. Must be between `5` and `600` (inclusive) if provided. Default behavior (when unset) is no heartbeat enforcement. + +This is useful for [presence channels](../channels/presence.mdx) and other use cases where accurate detection of dropped clients matters more than the default API Gateway idle timeout would provide. + +```json +{ + "scope": "connect", + // highlight-next-line + "heartbeatTimeout": 30 +} +``` + ## `iat` - Issued At {/* #iat */} `NumericDate` (optional) - The issued at claim identifies the time when the JWT was issued, expressed as a Unix timestamp. Hotsock does not rely on this claim for authorization, but it's common for issuers to provide it by default which is why it's mentioned here. diff --git a/docs/connections/client-http-api.mdx b/docs/connections/client-http-api.mdx index 33ee8de..5fb8f3e 100644 --- a/docs/connections/client-http-api.mdx +++ b/docs/connections/client-http-api.mdx @@ -5,7 +5,7 @@ toc_max_heading_level: 4 # Client HTTP API -In addition to WebSocket interactions, connected clients can also make use of an HTTP API for publishing messages, listing message history, reading and writing channel storage, and updating user metadata. All requests **_must_ be `POST` requests** and **_must_ have URL-encoded `connectionId` and `connectionSecret`** query parameters set. +In addition to WebSocket interactions, connected clients can also make use of an HTTP API for publishing messages, listing message history, listing reactions, reading and writing channel storage, and updating user metadata. All requests **_must_ be `POST` requests** and **_must_ have URL-encoded `connectionId` and `connectionSecret`** query parameters set. ## Determine your API URL {/* #determine-your-api-url */} @@ -57,7 +57,9 @@ List the message history for a channel. By default, a connection can only list m When issuing a connect or subscribe token, specify the [`historyStart`](./claims.mdx#channels.historyStart) claim to expand visibility to message history beyond the lifetime of the active subscription. -This endpoint will return up to 100 messages or up to 1MB of data, whichever comes first. Fetch subsequent pages using `before` or `after`, depending on your use case. +This endpoint will return up to `limit` messages (default 100) or up to 1MB of data, whichever comes first. Fetch subsequent pages using `before` or `after`, depending on your use case. + +If any returned messages have reactions, a `reactions` object is included on each message, keyed by reaction value with `{count, items?}` per entry. By default only `count` is set. Pass [`expandReactions: true`](#connection/listMessages.expandReactions) to also populate `items` with one entry per reacting user. #### `after` {/* #connection/listMessages.after */} @@ -79,6 +81,14 @@ Example: `01JA3HVNF6E89HDT1ABRFWJ8C8` String (required) - The name of the channel to query message history. +#### `expandReactions` {/* #connection/listMessages.expandReactions */} + +Boolean (optional) - If `true`, the `reactions` object on each returned message is enriched with an `items` array per reaction containing the `userId` and `userMetadata` of each reacting user. Default is `false`, which returns aggregated counts only. Messages with no reactions emit `{}` when `expandReactions` is `true` and omit the field otherwise. + +#### `limit` {/* #connection/listMessages.limit */} + +Integer (optional) - The maximum number of messages to return. Default is `100`. + #### `reverse` {/* #connection/listMessages.reverse */} Boolean (optional) - If set to `true`, lists messages from newest to oldest. Default is `false`. @@ -122,7 +132,9 @@ fetch( "meta": { "uid": "Jim", "umd": null - } + }, + // highlight-next-line + "reactions": { "thumbsup": { "count": 2 }, "heart": { "count": 1 } } }, { "id": "01JA3S0GB6S3WNTV2S1RJ421TH", @@ -152,7 +164,93 @@ fetch( "meta": { "uid": "Pam", "umd": null + }, + // highlight-next-line + "reactions": { "heart": { "count": 3 } } + } + ] +} +``` + +With `expandReactions: true`, each entry also has an `items` array with one element per reacting user: + +```json +{ + "messages": [ + { + "id": "01JA3S0P7FB7WVYS67X316M32S", + "event": "my-event", + "channel": "my-channel", + "data": "Wow. Can we make it a different moment?", + "meta": { "uid": "Jim", "umd": null }, + // highlight-start + "reactions": { + "thumbsup": { + "count": 2, + "items": [ + { "userId": "Jim", "userMetadata": { "name": "Jim Halpert" } }, + { "userId": "Pam" } + ] + }, + "heart": { + "count": 1, + "items": [{ "userId": "Dwight" }] + } } + // highlight-end + } + ] +} +``` + +## `connection/listReactions` {/* #connection/listReactions */} + +List the individual reactions on a specific stored message, including who reacted. The connection must be subscribed to the channel. + +#### `channel` {/* #connection/listReactions.channel */} + +`String` (required) - The name of the channel where the message was published. + +#### `messageId` {/* #connection/listReactions.messageId */} + +`String` (required) - The ID of the message to list reactions for. + +### Example {/* #connection/listReactions--example */} + +#### Request {/* #connection/listReactions--example-request */} + +```javascript +fetch( + "https://r6zcm2.lambda-url.us-east-1.on.aws/connection/listReactions?connectionId=fjlb_eHLIAMCKRg%3d&connectionSecret=SZy32Etv0KIbe4Jod6KH", + { + method: "POST", + body: JSON.stringify({ + channel: "my-channel", + messageId: "01JA3S0P7FB7WVYS67X316M32S", + }), + }, +) +``` + +#### Response {/* #connection/listReactions--example-response */} + +Expect a `200 OK` status code for successful reads. + +```json +{ + "reactions": [ + { + "reaction": "thumbsup", + "userId": "Jim", + "userMetadata": { "name": "Jim Halpert" } + }, + { + "reaction": "thumbsup", + "userId": "Pam" + }, + { + "reaction": "heart", + "userId": "Dwight" } ] } diff --git a/docs/connections/keep-alive.mdx b/docs/connections/keep-alive.mdx index b2695f3..d6313fe 100644 --- a/docs/connections/keep-alive.mdx +++ b/docs/connections/keep-alive.mdx @@ -37,24 +37,19 @@ wscat -c 0.20s user 0.07s system 0% cpu 2:00:00.46 total Clients should not reply to server-initiated `hotsock.keepAlive` events. -{/* ## Send `hotsock.heartbeat` +## Send `hotsock.heartbeat` {/* #send-hotsockheartbeat */} Like other TCP-based protocols, WebSockets will happily assume that connections are still alive until proven otherwise or until they are explicitly closed. There are many circumstances, however, where the connection is dead but one side doesn't know it yet. Unexpected network disconnects and computers going to sleep are some examples of where this can happen. -Both [standard](../channels/standard) and [presence](../channels/presence) channels support optional [connection heartbeats](./claims.mdx). +This is particularly useful for [presence channels](../channels/presence.mdx) when accuracy of connected members is critical and your application cannot withstand a delay in detecting dropped connections. -This is particularly useful for [presence channels](../channels/presence) when accuracy of connected members is critical and your application cannot withstand a delay in detecting dropped connections. - -If enabled, the client _must_ send a `hotsock.heartbeat` event on the WebSocket at least as often as specified in the `clientHeartbeatInterval` claim. - -The backend records receipt of this message, but does not send a reply. Avoid over-sending heartbeats, as each heartbeat incurs a database write and a billable API Gateway message. +When the [`heartbeatTimeout` claim](./claims.mdx#heartbeatTimeout) is set, the client _must_ send a `hotsock.heartbeat` event on the WebSocket at least every `heartbeatTimeout` seconds. The server records receipt of this message but does not send a reply. Avoid over-sending heartbeats — each heartbeat incurs a database write and a billable API Gateway message. ``` > {"event":"hotsock.heartbeat"} -> ``` -If the server does not receive a heartbeat message from the client within the timeframe specified by the `clientHeartbeatInterval` claim, that connection is forcefully closed by the server. */} +If the server does not receive a heartbeat message within the configured timeout, the connection is forcefully closed by the server. ## `hotsock.ping` and `hotsock.pong` {/* #hotsockping-and-hotsockpong */} diff --git a/docs/installation/changelog.mdx b/docs/installation/changelog.mdx index b648550..ca5f6fd 100644 --- a/docs/installation/changelog.mdx +++ b/docs/installation/changelog.mdx @@ -1,5 +1,24 @@ # Changelog +## v1.13.1 - May 18, 2026 {/* #v1.13.1 */} + +- Add [message reactions](../connections/claims.mdx#channels.messages.react). Clients send `hotsock.messageReaction` events with `add` or `remove` actions, gated by a new [`react`](../connections/claims.mdx#channels.messages.react) directive on channel message event patterns. Reactions require an active channel subscription, a [`uid`](../connections/claims.mdx#uid), and a stored target message that hasn't expired. Each user can have one reaction per reaction value per message. Also available via the [Client HTTP API](../connections/client-http-api.mdx) and the server-side [Lambda](../server-api/publish-messages.mdx#publish-with-lambda) and [HTTP URL](../server-api/publish-messages.mdx#publish-with-http-url) publish APIs. +- Reaction counts are automatically included in [`listMessages`](../connections/client-http-api.mdx#connection/listMessages) responses on each message that has reactions. Pass [`expandReactions: true`](../connections/client-http-api.mdx#connection/listMessages.expandReactions) to also receive a list of reacting users per reaction value. +- Add [`connection/listReactions`](../connections/client-http-api.mdx#connection/listReactions) Client HTTP API endpoint to list individual reactions on a single message with user attribution. +- Add [`hotsock.messageReactionAdded`](../server-api/events.mdx#hotsock.messageReactionAdded) and [`hotsock.messageReactionRemoved`](../server-api/events.mdx#hotsock.messageReactionRemoved) pub/sub events to SNS/EventBridge when reactions are added or removed. Always emitted — no `emitPubSubEvent` directive is required. +- Add configurable [`limit`](../connections/client-http-api.mdx#connection/listMessages.limit) parameter to [`connection/listMessages`](../connections/client-http-api.mdx#connection/listMessages) (default 100). +- Publish [`hotsock.memberAdded`](../server-api/events.mdx#hotsock.memberAdded), [`hotsock.memberRemoved`](../server-api/events.mdx#hotsock.memberRemoved), and [`hotsock.memberUpdated`](../server-api/events.mdx#hotsock.memberUpdated) presence channel events to SNS/EventBridge, deduplicated by `uid` to match WebSocket fan-out semantics. +- Add [`heartbeatTimeout`](../connections/claims.mdx#heartbeatTimeout) connect-token claim for server-enforced connection heartbeats. When set (5–600 seconds), the client must send a [`hotsock.heartbeat`](../connections/keep-alive.mdx#send-hotsockheartbeat) message at least that often or the server will forcefully disconnect. Useful for [presence channels](../channels/presence.mdx) and other applications that need accurate dropped-connection detection beyond what API Gateway's idle timeout provides. +- Improve subscription cleanup throughput when many connections disconnect or unsubscribe at the same time. +- Tighten heartbeat-check scheduling so stale connections are disconnected closer to their actual deadline. +- Update the [Web Console](../server-api/web-console.mdx) to the latest build. +- Build with Go 1.26.3. +- Update all aws-sdk-go-v2 SDK modules to their latest versions (as of [2026-05-12](https://github.com/aws/aws-sdk-go-v2/releases/tag/release-2026-05-12)). + +## v1.13.0 - May 18, 2026 {/* #v1.13.0 */} + +- Skip this release. See [v1.13.1](#v1.13.1) for the full list of changes. + ## v1.12.0 - April 8, 2026 {/* #v1.12.0 */} - Add [channel storage](../connections/claims.mdx#channels.storage), a per-key persistent key-value store on channels. Storage permissions are configured per-key with wildcard and regex pattern support. Entries have independent TTLs, are delivered to [`observe`](../connections/claims.mdx#channels.storage.observe) subscribers on join, and skip fan-out when the value hasn't changed. Clients interact with storage via [`hotsock.channelStorageSet`](../connections/claims.mdx#channels.storage.set) / [`hotsock.channelStorageGet`](../connections/claims.mdx#channels.storage.get) on the WebSocket or the [`connection/channelStorageGet`](../connections/client-http-api.mdx#connection/channelStorageGet), [`connection/channelStorageSet`](../connections/client-http-api.mdx#connection/channelStorageSet), and [`connection/channelStorageList`](../connections/client-http-api.mdx#connection/channelStorageList) Client HTTP API endpoints. Storage operations do not require an active channel subscription. Server-side writes are available via the Lambda and HTTP publish APIs using `"event": "hotsock.channelStorageSet"` with a `key` field. diff --git a/docs/server-api/events.mdx b/docs/server-api/events.mdx index d60ec03..213f267 100644 --- a/docs/server-api/events.mdx +++ b/docs/server-api/events.mdx @@ -506,3 +506,203 @@ SNS filter attributes are limited to `channel`, `key`, and `trigger`. ### `dataType` {/* #hotsock.channelStorageUpdated--dataType */} The data object type for this event is `channelStorage`. + +## `hotsock.messageReactionAdded` {/* #hotsock.messageReactionAdded */} + +This event is sent whenever a reaction is added to a stored message. Reaction events are always emitted to SNS/EventBridge — no `emitPubSubEvent` directive is required. + +### `type` {/* #hotsock.messageReactionAdded--type */} + +This is always `hotsock.messageReactionAdded`. + +### `metadata` {/* #hotsock.messageReactionAdded--metadata */} + +- `channel` (String): The name of the channel where the reaction was added. +- `connectionId` (String): The identifier for the connection that added the reaction. This field is only present for reactions initiated by a WebSocket client or Client HTTP API. +- `event` (String): This is always `hotsock.messageReaction`. +- `hotsockVersion` (String): The version of the Hotsock installation that generated this event. +- `requestId` (String): The request ID for the Lambda invocation that originally accepted the message. +- `sourceIp` (String): The IP address of the client that added the reaction. This field is only present if an IP address is known. +- `trigger` (String): The kind of initiator, set to one of `client.websocket`, `client.http`, `server.lambda`, or `server.http`. +- `userAgent` (String): The User-Agent of the client that added the reaction. This field is only present if a user agent is known. + +```json +{ + "channel": "my-channel", + "connectionId": "IDnAdd9kIAMCEsQ=", + "event": "hotsock.messageReaction", + "hotsockVersion": "1.13.0", + "requestId": "9d1d380a-0152-4962-bbbb-6e721b81db52", + "sourceIp": "12.34.56.78", + "trigger": "client.websocket", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" +} +``` + +### `data` {/* #hotsock.messageReactionAdded--data */} + +- `channel` (String): The name of the channel where the reaction was added. +- `event` (String): This is always `hotsock.messageReactionAdded`. +- `id` (String): The unique ID of this reaction event ([ULID](https://github.com/ulid/spec)). +- `data` (Object): The reaction payload, containing `messageId`, `reaction`, and `action`. +- `meta` (Object): Metadata about the user who added the reaction, including `uid` and `umd`. + +```json +{ + "channel": "my-channel", + "event": "hotsock.messageReactionAdded", + "id": "01JB4K7M2NC8QBYMZRBMCEXGNW", + "data": { + "messageId": "01JA3S0TNEC2QBYMZRBMCEXGNW", + "reaction": "thumbsup", + "action": "add" + }, + "meta": { "uid": "Jim", "umd": null } +} +``` + +### `dataType` {/* #hotsock.messageReactionAdded--dataType */} + +The data object type for this event is `message`. + +## `hotsock.messageReactionRemoved` {/* #hotsock.messageReactionRemoved */} + +This event is sent whenever a reaction is removed from a stored message. Reaction events are always emitted to SNS/EventBridge — no `emitPubSubEvent` directive is required. + +### `type` {/* #hotsock.messageReactionRemoved--type */} + +This is always `hotsock.messageReactionRemoved`. + +### `metadata` {/* #hotsock.messageReactionRemoved--metadata */} + +Same as [`hotsock.messageReactionAdded` metadata](#hotsock.messageReactionAdded--metadata). + +### `data` {/* #hotsock.messageReactionRemoved--data */} + +- `channel` (String): The name of the channel where the reaction was removed. +- `event` (String): This is always `hotsock.messageReactionRemoved`. +- `id` (String): The unique ID of this reaction event ([ULID](https://github.com/ulid/spec)). +- `data` (Object): The reaction payload, containing `messageId`, `reaction`, and `action`. +- `meta` (Object): Metadata about the user who removed the reaction, including `uid` and `umd`. + +```json +{ + "channel": "my-channel", + "event": "hotsock.messageReactionRemoved", + "id": "01JB4K8P3QD9RCZMASCNDFY0HX", + "data": { + "messageId": "01JA3S0TNEC2QBYMZRBMCEXGNW", + "reaction": "thumbsup", + "action": "remove" + }, + "meta": { "uid": "Jim", "umd": null } +} +``` + +### `dataType` {/* #hotsock.messageReactionRemoved--dataType */} + +The data object type for this event is `message`. + +## `hotsock.memberAdded` {/* #hotsock.memberAdded */} + +This event is sent whenever a unique member is added to a [presence channel](../channels/presence.mdx). Member events are deduplicated by `uid` to match WebSocket fan-out semantics — if a user is already present on the channel from another connection, no `hotsock.memberAdded` event fires when an additional connection joins. + +### `type` {/* #hotsock.memberAdded--type */} + +This is always `hotsock.memberAdded`. + +### `metadata` {/* #hotsock.memberAdded--metadata */} + +- `channel` (String): The name of the presence channel the member joined. +- `hotsockVersion` (String): The version of the Hotsock installation that generated this event. + +```json +{ + "channel": "presence.chat", + "hotsockVersion": "1.13.0" +} +``` + +### `data` {/* #hotsock.memberAdded--data */} + +- `uid` (String): The user ID of the member that was added. +- `umd` (JSON | null): The user metadata of the member that was added. +- `members` (Array): The full deduplicated member list for the channel after the change. + +```json +{ + "uid": "Dwight", + "umd": { "status": "available" }, + "members": [ + { "uid": "Jim", "umd": null }, + { "uid": "Dwight", "umd": { "status": "available" } } + ] +} +``` + +### `dataType` {/* #hotsock.memberAdded--dataType */} + +The data object type for this event is `channelMember`. + +## `hotsock.memberRemoved` {/* #hotsock.memberRemoved */} + +This event is sent whenever the last subscription for a `uid` on a [presence channel](../channels/presence.mdx) ends. If the same user is still subscribed from another connection, no `hotsock.memberRemoved` event fires. + +### `type` {/* #hotsock.memberRemoved--type */} + +This is always `hotsock.memberRemoved`. + +### `metadata` {/* #hotsock.memberRemoved--metadata */} + +Same as [`hotsock.memberAdded` metadata](#hotsock.memberAdded--metadata). + +### `data` {/* #hotsock.memberRemoved--data */} + +- `uid` (String): The user ID of the member that was removed. +- `umd` (JSON | null): The last-known user metadata of the member that was removed. +- `members` (Array): The full deduplicated member list for the channel after the change. + +```json +{ + "uid": "Dwight", + "umd": { "status": "available" }, + "members": [{ "uid": "Jim", "umd": null }] +} +``` + +### `dataType` {/* #hotsock.memberRemoved--dataType */} + +The data object type for this event is `channelMember`. + +## `hotsock.memberUpdated` {/* #hotsock.memberUpdated */} + +This event is sent whenever a presence channel member's user metadata changes — either through a [`hotsock.umdUpdate`](../connections/claims.mdx#channels.umdUpdate) command, a connection-level [`umdUpdate`](../connections/claims.mdx#umdUpdate) with [`umdPropagate`](../connections/claims.mdx#umdPropagate), or a duplicate `uid` subscribing with different `umd`. + +### `type` {/* #hotsock.memberUpdated--type */} + +This is always `hotsock.memberUpdated`. + +### `metadata` {/* #hotsock.memberUpdated--metadata */} + +Same as [`hotsock.memberAdded` metadata](#hotsock.memberAdded--metadata). + +### `data` {/* #hotsock.memberUpdated--data */} + +- `uid` (String): The user ID of the member whose metadata changed. +- `umd` (JSON | null): The updated user metadata. +- `members` (Array): The full deduplicated member list for the channel, reflecting the updated metadata. + +```json +{ + "uid": "Dwight", + "umd": { "status": "away" }, + "members": [ + { "uid": "Jim", "umd": null }, + { "uid": "Dwight", "umd": { "status": "away" } } + ] +} +``` + +### `dataType` {/* #hotsock.memberUpdated--dataType */} + +The data object type for this event is `channelMember`. From 21d65273dd4f84c1ae043f66597b9135f45ea45b Mon Sep 17 00:00:00 2001 From: James Miller Date: Mon, 18 May 2026 12:59:22 -0700 Subject: [PATCH 02/34] blog: clarify presence member events as pub/sub (SNS/EventBridge) --- blog/2026-05-18-new-in-v1.13.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/2026-05-18-new-in-v1.13.mdx b/blog/2026-05-18-new-in-v1.13.mdx index 76b087f..2f45e59 100644 --- a/blog/2026-05-18-new-in-v1.13.mdx +++ b/blog/2026-05-18-new-in-v1.13.mdx @@ -96,7 +96,7 @@ You can also fetch reactions for a single message with the dedicated [`connectio [`hotsock.messageReactionAdded`](/docs/server-api/events/#hotsock.messageReactionAdded) and [`hotsock.messageReactionRemoved`](/docs/server-api/events/#hotsock.messageReactionRemoved) events are always emitted to SNS/EventBridge when reactions are added or removed — no `emitPubSubEvent` directive needed. This makes server-side aggregation, moderation, or analytics straightforward. -### Presence member events on SNS/EventBridge {/* #presence-member-pubsub */} +### Presence member events on pub/sub (SNS/EventBridge) {/* #presence-member-pubsub */} Presence channel member lifecycle events ([`hotsock.memberAdded`](/docs/server-api/events/#hotsock.memberAdded), [`hotsock.memberRemoved`](/docs/server-api/events/#hotsock.memberRemoved), [`hotsock.memberUpdated`](/docs/server-api/events/#hotsock.memberUpdated)) are now also emitted to SNS/EventBridge, gated by the existing `PublishEventsToSNSParameter` / `PublishEventsToEventBridgeParameter` settings. The pub/sub events are deduplicated by `uid` to match WebSocket fan-out semantics — joining from a second device when you're already present doesn't trigger another `memberAdded`. From 43a2788fabe993bf0a29ef770d93cf669cd411a6 Mon Sep 17 00:00:00 2001 From: James Miller Date: Mon, 18 May 2026 13:00:12 -0700 Subject: [PATCH 03/34] blog: drop expandReactions mention from intro --- blog/2026-05-18-new-in-v1.13.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/2026-05-18-new-in-v1.13.mdx b/blog/2026-05-18-new-in-v1.13.mdx index 2f45e59..2ed7e83 100644 --- a/blog/2026-05-18-new-in-v1.13.mdx +++ b/blog/2026-05-18-new-in-v1.13.mdx @@ -6,7 +6,7 @@ tags: [releases, features, channels, presence] description: Hotsock v1.13 adds message reactions with per-event permissions and real-time delivery, presence member events to SNS/EventBridge, and a heartbeatTimeout claim for server-enforced connection liveness. --- -Hotsock v1.13 introduces **message reactions**, **presence member events on SNS/EventBridge**, and a **`heartbeatTimeout` claim** for server-enforced connection liveness. The release also adds an `expandReactions` flag to `listMessages` so you can fetch reaction details with user attribution in a single call. +Hotsock v1.13 introduces **message reactions**, **presence member events on pub/sub (SNS/EventBridge)**, and a **`heartbeatTimeout` claim** for server-enforced connection liveness. {/* truncate */} From c7e1479579f43ad8e5be6367ee1d13043f463acc Mon Sep 17 00:00:00 2001 From: James Miller Date: Mon, 18 May 2026 13:01:37 -0700 Subject: [PATCH 04/34] blog: link all claims and directives --- blog/2026-05-18-new-in-v1.13.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/blog/2026-05-18-new-in-v1.13.mdx b/blog/2026-05-18-new-in-v1.13.mdx index 2ed7e83..37025c6 100644 --- a/blog/2026-05-18-new-in-v1.13.mdx +++ b/blog/2026-05-18-new-in-v1.13.mdx @@ -14,7 +14,7 @@ Hotsock v1.13 introduces **message reactions**, **presence member events on pub/ Clients can now add and remove reactions on stored messages, with real-time delivery to all channel subscribers, aggregated counts in message history, and a separate endpoint to list who reacted with what. -Reactions are gated by a new [`react`](/docs/connections/claims/#channels.messages.react) directive on channel message event patterns, alongside the existing `publish`, `store`, `echo`, and other directives. A connection needs three things to react: an active channel subscription, a [`uid`](/docs/connections/claims/#uid), and a stored target message that hasn't expired. +Reactions are gated by a new [`react`](/docs/connections/claims/#channels.messages.react) directive on channel message event patterns, alongside the existing [`publish`](/docs/connections/claims/#channels.messages.publish), [`store`](/docs/connections/claims/#channels.messages.store), [`echo`](/docs/connections/claims/#channels.messages.echo), and other directives. A connection needs three things to react: an active channel subscription, a [`uid`](/docs/connections/claims/#uid), and a stored target message that hasn't expired. #### Setting up permissions {/* #setting-up-permissions */} @@ -94,13 +94,13 @@ You can also fetch reactions for a single message with the dedicated [`connectio #### Pub/sub events for reactions {/* #pubsub-events-for-reactions */} -[`hotsock.messageReactionAdded`](/docs/server-api/events/#hotsock.messageReactionAdded) and [`hotsock.messageReactionRemoved`](/docs/server-api/events/#hotsock.messageReactionRemoved) events are always emitted to SNS/EventBridge when reactions are added or removed — no `emitPubSubEvent` directive needed. This makes server-side aggregation, moderation, or analytics straightforward. +[`hotsock.messageReactionAdded`](/docs/server-api/events/#hotsock.messageReactionAdded) and [`hotsock.messageReactionRemoved`](/docs/server-api/events/#hotsock.messageReactionRemoved) events are always emitted to SNS/EventBridge when reactions are added or removed — no [`emitPubSubEvent`](/docs/connections/claims/#channels.messages.emitPubSubEvent) directive needed. This makes server-side aggregation, moderation, or analytics straightforward. ### Presence member events on pub/sub (SNS/EventBridge) {/* #presence-member-pubsub */} -Presence channel member lifecycle events ([`hotsock.memberAdded`](/docs/server-api/events/#hotsock.memberAdded), [`hotsock.memberRemoved`](/docs/server-api/events/#hotsock.memberRemoved), [`hotsock.memberUpdated`](/docs/server-api/events/#hotsock.memberUpdated)) are now also emitted to SNS/EventBridge, gated by the existing `PublishEventsToSNSParameter` / `PublishEventsToEventBridgeParameter` settings. The pub/sub events are deduplicated by `uid` to match WebSocket fan-out semantics — joining from a second device when you're already present doesn't trigger another `memberAdded`. +Presence channel member lifecycle events ([`hotsock.memberAdded`](/docs/server-api/events/#hotsock.memberAdded), [`hotsock.memberRemoved`](/docs/server-api/events/#hotsock.memberRemoved), [`hotsock.memberUpdated`](/docs/server-api/events/#hotsock.memberUpdated)) are now also emitted to SNS/EventBridge, gated by the existing [`PublishEventsToSNSParameter` / `PublishEventsToEventBridgeParameter`](/docs/server-api/events/#eventbridge-sns-enable-disable) settings. The pub/sub events are deduplicated by [`uid`](/docs/connections/claims/#uid) to match WebSocket fan-out semantics — joining from a second device when you're already present doesn't trigger another `memberAdded`. -The pub/sub `data` payload uses the same single-entity shape as the rest of the public event types, with `uid`, `umd`, and `members` at the root: +The pub/sub `data` payload uses the same single-entity shape as the rest of the public event types, with [`uid`](/docs/connections/claims/#uid), [`umd`](/docs/connections/claims/#umd), and `members` at the root: ```json { @@ -117,7 +117,7 @@ The pub/sub `data` payload uses the same single-entity shape as the rest of the ### Server-enforced heartbeats {/* #server-enforced-heartbeats */} -The new [`heartbeatTimeout`](/docs/connections/claims/#heartbeatTimeout) connect-token claim sets a maximum interval between client-initiated `hotsock.heartbeat` messages. If the server doesn't see a heartbeat within that window, the connection is forcefully disconnected: +The new [`heartbeatTimeout`](/docs/connections/claims/#heartbeatTimeout) connect-token claim sets a maximum interval between client-initiated [`hotsock.heartbeat`](/docs/connections/keep-alive/#send-hotsockheartbeat) messages. If the server doesn't see a heartbeat within that window, the connection is forcefully disconnected: ```json { From 5319d2aa4f805403df0c0833723ef7aa46d60522 Mon Sep 17 00:00:00 2001 From: James Miller Date: Mon, 18 May 2026 13:02:32 -0700 Subject: [PATCH 05/34] blog: drop implementation detail about reaction counts --- blog/2026-05-18-new-in-v1.13.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/2026-05-18-new-in-v1.13.mdx b/blog/2026-05-18-new-in-v1.13.mdx index 37025c6..ccebd66 100644 --- a/blog/2026-05-18-new-in-v1.13.mdx +++ b/blog/2026-05-18-new-in-v1.13.mdx @@ -76,7 +76,7 @@ Reaction data can be emoji characters, Slack-style colon-wrapped strings like `: } ``` -Counts are returned for free — they come from a metadata item adjacent to each message that gets fetched by the same query that loads messages. Pass [`expandReactions: true`](/docs/connections/client-http-api/#connection/listMessages.expandReactions) to also include an `items` array per reaction with the user behind each one: +Pass [`expandReactions: true`](/docs/connections/client-http-api/#connection/listMessages.expandReactions) to also include an `items` array per reaction with the user behind each one: ```json "reactions": { From 42ee8758967f6d63f4c1c22fe2103d0e4cf248e4 Mon Sep 17 00:00:00 2001 From: James Miller Date: Mon, 18 May 2026 13:20:57 -0700 Subject: [PATCH 06/34] release v1.13.2 docs, skip v1.13.0 and v1.13.1 --- blog/2026-05-18-new-in-v1.13.mdx | 6 +++--- docs/connections/client-http-api.mdx | 16 ++++++++-------- docs/installation/changelog.mdx | 8 ++++++-- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/blog/2026-05-18-new-in-v1.13.mdx b/blog/2026-05-18-new-in-v1.13.mdx index ccebd66..f92922b 100644 --- a/blog/2026-05-18-new-in-v1.13.mdx +++ b/blog/2026-05-18-new-in-v1.13.mdx @@ -83,8 +83,8 @@ Pass [`expandReactions: true`](/docs/connections/client-http-api/#connection/lis "thumbsup": { "count": 2, "items": [ - { "userId": "Jim", "userMetadata": { "name": "Jim Halpert" } }, - { "userId": "Pam" } + { "uid": "Jim", "umd": { "name": "Jim Halpert" } }, + { "uid": "Pam" } ] } } @@ -137,4 +137,4 @@ Valid values are 5–600 seconds. This is most useful on [presence channels](/do ### Wrapping up {/* #wrapping-up */} -Existing installations with auto-update enabled are already running v1.13.1 and have access to these features today. Other installations can be [manually updated](/docs/installation/updates/#manually-update-installation) at any time. A [full changelog](/docs/installation/changelog/#v1.13.1) is available with the complete list of changes included in this release. +Existing installations with auto-update enabled are already running v1.13.2 and have access to these features today. Other installations can be [manually updated](/docs/installation/updates/#manually-update-installation) at any time. A [full changelog](/docs/installation/changelog/#v1.13.2) is available with the complete list of changes included in this release. diff --git a/docs/connections/client-http-api.mdx b/docs/connections/client-http-api.mdx index 5fb8f3e..40aee6f 100644 --- a/docs/connections/client-http-api.mdx +++ b/docs/connections/client-http-api.mdx @@ -83,7 +83,7 @@ String (required) - The name of the channel to query message history. #### `expandReactions` {/* #connection/listMessages.expandReactions */} -Boolean (optional) - If `true`, the `reactions` object on each returned message is enriched with an `items` array per reaction containing the `userId` and `userMetadata` of each reacting user. Default is `false`, which returns aggregated counts only. Messages with no reactions emit `{}` when `expandReactions` is `true` and omit the field otherwise. +Boolean (optional) - If `true`, the `reactions` object on each returned message is enriched with an `items` array per reaction containing the `uid` and `umd` of each reacting user. Default is `false`, which returns aggregated counts only. Messages with no reactions emit `{}` when `expandReactions` is `true` and omit the field otherwise. #### `limit` {/* #connection/listMessages.limit */} @@ -188,13 +188,13 @@ With `expandReactions: true`, each entry also has an `items` array with one elem "thumbsup": { "count": 2, "items": [ - { "userId": "Jim", "userMetadata": { "name": "Jim Halpert" } }, - { "userId": "Pam" } + { "uid": "Jim", "umd": { "name": "Jim Halpert" } }, + { "uid": "Pam" } ] }, "heart": { "count": 1, - "items": [{ "userId": "Dwight" }] + "items": [{ "uid": "Dwight" }] } } // highlight-end @@ -241,16 +241,16 @@ Expect a `200 OK` status code for successful reads. "reactions": [ { "reaction": "thumbsup", - "userId": "Jim", - "userMetadata": { "name": "Jim Halpert" } + "uid": "Jim", + "umd": { "name": "Jim Halpert" } }, { "reaction": "thumbsup", - "userId": "Pam" + "uid": "Pam" }, { "reaction": "heart", - "userId": "Dwight" + "uid": "Dwight" } ] } diff --git a/docs/installation/changelog.mdx b/docs/installation/changelog.mdx index ca5f6fd..b6080a1 100644 --- a/docs/installation/changelog.mdx +++ b/docs/installation/changelog.mdx @@ -1,6 +1,6 @@ # Changelog -## v1.13.1 - May 18, 2026 {/* #v1.13.1 */} +## v1.13.2 - May 18, 2026 {/* #v1.13.2 */} - Add [message reactions](../connections/claims.mdx#channels.messages.react). Clients send `hotsock.messageReaction` events with `add` or `remove` actions, gated by a new [`react`](../connections/claims.mdx#channels.messages.react) directive on channel message event patterns. Reactions require an active channel subscription, a [`uid`](../connections/claims.mdx#uid), and a stored target message that hasn't expired. Each user can have one reaction per reaction value per message. Also available via the [Client HTTP API](../connections/client-http-api.mdx) and the server-side [Lambda](../server-api/publish-messages.mdx#publish-with-lambda) and [HTTP URL](../server-api/publish-messages.mdx#publish-with-http-url) publish APIs. - Reaction counts are automatically included in [`listMessages`](../connections/client-http-api.mdx#connection/listMessages) responses on each message that has reactions. Pass [`expandReactions: true`](../connections/client-http-api.mdx#connection/listMessages.expandReactions) to also receive a list of reacting users per reaction value. @@ -15,9 +15,13 @@ - Build with Go 1.26.3. - Update all aws-sdk-go-v2 SDK modules to their latest versions (as of [2026-05-12](https://github.com/aws/aws-sdk-go-v2/releases/tag/release-2026-05-12)). +## v1.13.1 - May 18, 2026 {/* #v1.13.1 */} + +- Skip this release. See [v1.13.2](#v1.13.2) for the full list of changes. + ## v1.13.0 - May 18, 2026 {/* #v1.13.0 */} -- Skip this release. See [v1.13.1](#v1.13.1) for the full list of changes. +- Skip this release. See [v1.13.2](#v1.13.2) for the full list of changes. ## v1.12.0 - April 8, 2026 {/* #v1.12.0 */} From 986a3d2462da38681e67afa5352bcb360d68e0ee Mon Sep 17 00:00:00 2001 From: James Miller Date: Mon, 18 May 2026 13:24:49 -0700 Subject: [PATCH 07/34] blog: remove em-dashes --- blog/2026-05-18-new-in-v1.13.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/blog/2026-05-18-new-in-v1.13.mdx b/blog/2026-05-18-new-in-v1.13.mdx index f92922b..4f62c0a 100644 --- a/blog/2026-05-18-new-in-v1.13.mdx +++ b/blog/2026-05-18-new-in-v1.13.mdx @@ -54,7 +54,7 @@ All channel subscribers immediately receive a `hotsock.messageReactionAdded` eve < {"id":"01JB4K7M2N...","event":"hotsock.messageReactionAdded","channel":"room.123","data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW","reaction":"thumbsup","action":"add"},"meta":{"uid":"Jim","umd":null}} ``` -Reaction data can be emoji characters, Slack-style colon-wrapped strings like `:thumbsup:`, or any short string up to 100 bytes. Each user can have one reaction per reaction value per message — adding the same reaction again is a no-op. +Reaction data can be emoji characters, Slack-style colon-wrapped strings like `:thumbsup:`, or any short string up to 100 bytes. Each user can have one reaction per reaction value per message, and adding the same reaction again is a no-op. #### Reactions in message history {/* #reactions-in-message-history */} @@ -94,11 +94,11 @@ You can also fetch reactions for a single message with the dedicated [`connectio #### Pub/sub events for reactions {/* #pubsub-events-for-reactions */} -[`hotsock.messageReactionAdded`](/docs/server-api/events/#hotsock.messageReactionAdded) and [`hotsock.messageReactionRemoved`](/docs/server-api/events/#hotsock.messageReactionRemoved) events are always emitted to SNS/EventBridge when reactions are added or removed — no [`emitPubSubEvent`](/docs/connections/claims/#channels.messages.emitPubSubEvent) directive needed. This makes server-side aggregation, moderation, or analytics straightforward. +[`hotsock.messageReactionAdded`](/docs/server-api/events/#hotsock.messageReactionAdded) and [`hotsock.messageReactionRemoved`](/docs/server-api/events/#hotsock.messageReactionRemoved) events are always emitted to SNS/EventBridge when reactions are added or removed, with no [`emitPubSubEvent`](/docs/connections/claims/#channels.messages.emitPubSubEvent) directive needed. This makes server-side aggregation, moderation, or analytics straightforward. ### Presence member events on pub/sub (SNS/EventBridge) {/* #presence-member-pubsub */} -Presence channel member lifecycle events ([`hotsock.memberAdded`](/docs/server-api/events/#hotsock.memberAdded), [`hotsock.memberRemoved`](/docs/server-api/events/#hotsock.memberRemoved), [`hotsock.memberUpdated`](/docs/server-api/events/#hotsock.memberUpdated)) are now also emitted to SNS/EventBridge, gated by the existing [`PublishEventsToSNSParameter` / `PublishEventsToEventBridgeParameter`](/docs/server-api/events/#eventbridge-sns-enable-disable) settings. The pub/sub events are deduplicated by [`uid`](/docs/connections/claims/#uid) to match WebSocket fan-out semantics — joining from a second device when you're already present doesn't trigger another `memberAdded`. +Presence channel member lifecycle events ([`hotsock.memberAdded`](/docs/server-api/events/#hotsock.memberAdded), [`hotsock.memberRemoved`](/docs/server-api/events/#hotsock.memberRemoved), [`hotsock.memberUpdated`](/docs/server-api/events/#hotsock.memberUpdated)) are now also emitted to SNS/EventBridge, gated by the existing [`PublishEventsToSNSParameter` / `PublishEventsToEventBridgeParameter`](/docs/server-api/events/#eventbridge-sns-enable-disable) settings. The pub/sub events are deduplicated by [`uid`](/docs/connections/claims/#uid) to match WebSocket fan-out semantics, so joining from a second device when you're already present doesn't trigger another `memberAdded`. The pub/sub `data` payload uses the same single-entity shape as the rest of the public event types, with [`uid`](/docs/connections/claims/#uid), [`umd`](/docs/connections/claims/#umd), and `members` at the root: @@ -127,7 +127,7 @@ The new [`heartbeatTimeout`](/docs/connections/claims/#heartbeatTimeout) connect } ``` -Valid values are 5–600 seconds. This is most useful on [presence channels](/docs/channels/presence/) where accurate detection of dropped clients matters more than waiting for API Gateway's 10-minute idle timeout. The client just needs to send `{"event":"hotsock.heartbeat"}` periodically — the server records receipt and reschedules the next check relative to the last heartbeat, with no reply. +Valid values are 5 to 600 seconds. This is most useful on [presence channels](/docs/channels/presence/) where accurate detection of dropped clients matters more than waiting for API Gateway's 10-minute idle timeout. The client just needs to send `{"event":"hotsock.heartbeat"}` periodically, and the server records receipt and reschedules the next check relative to the last heartbeat, with no reply. ### Other improvements {/* #other-improvements */} From 54303815749dee7cf519f233ce1ff90b33c10963 Mon Sep 17 00:00:00 2001 From: James Miller Date: Mon, 18 May 2026 13:25:49 -0700 Subject: [PATCH 08/34] blog: trim heartbeat tightening and Go version from other improvements --- blog/2026-05-18-new-in-v1.13.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/blog/2026-05-18-new-in-v1.13.mdx b/blog/2026-05-18-new-in-v1.13.mdx index 4f62c0a..ec32961 100644 --- a/blog/2026-05-18-new-in-v1.13.mdx +++ b/blog/2026-05-18-new-in-v1.13.mdx @@ -132,8 +132,7 @@ Valid values are 5 to 600 seconds. This is most useful on [presence channels](/d ### Other improvements {/* #other-improvements */} - Faster subscription cleanup when many connections disconnect or unsubscribe at the same time. -- Tighter heartbeat-check scheduling so stale connections are disconnected closer to their actual deadline. -- Updated to Go 1.26.3 and the [latest aws-sdk-go-v2 modules](https://github.com/aws/aws-sdk-go-v2/releases/tag/release-2026-05-12). +- Updated to the [latest aws-sdk-go-v2 modules](https://github.com/aws/aws-sdk-go-v2/releases/tag/release-2026-05-12). ### Wrapping up {/* #wrapping-up */} From a4fb63c10cfee09332274e35727ec21d26cf34ef Mon Sep 17 00:00:00 2001 From: James Miller Date: Mon, 18 May 2026 13:26:29 -0700 Subject: [PATCH 09/34] blog: drop other improvements section --- blog/2026-05-18-new-in-v1.13.mdx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/blog/2026-05-18-new-in-v1.13.mdx b/blog/2026-05-18-new-in-v1.13.mdx index ec32961..5eea56c 100644 --- a/blog/2026-05-18-new-in-v1.13.mdx +++ b/blog/2026-05-18-new-in-v1.13.mdx @@ -129,11 +129,6 @@ The new [`heartbeatTimeout`](/docs/connections/claims/#heartbeatTimeout) connect Valid values are 5 to 600 seconds. This is most useful on [presence channels](/docs/channels/presence/) where accurate detection of dropped clients matters more than waiting for API Gateway's 10-minute idle timeout. The client just needs to send `{"event":"hotsock.heartbeat"}` periodically, and the server records receipt and reschedules the next check relative to the last heartbeat, with no reply. -### Other improvements {/* #other-improvements */} - -- Faster subscription cleanup when many connections disconnect or unsubscribe at the same time. -- Updated to the [latest aws-sdk-go-v2 modules](https://github.com/aws/aws-sdk-go-v2/releases/tag/release-2026-05-12). - ### Wrapping up {/* #wrapping-up */} Existing installations with auto-update enabled are already running v1.13.2 and have access to these features today. Other installations can be [manually updated](/docs/installation/updates/#manually-update-installation) at any time. A [full changelog](/docs/installation/changelog/#v1.13.2) is available with the complete list of changes included in this release. From 20ebf6c8f5f6d8a65b5499a6f4afc44ff03491ea Mon Sep 17 00:00:00 2001 From: James Miller Date: Mon, 18 May 2026 13:37:55 -0700 Subject: [PATCH 10/34] homepage: surface v1.13 message reactions and presence pub/sub --- src/components/HomepageBanner.js | 8 ++++---- src/components/landing/Capabilities.js | 20 +++++++++++++++++--- src/components/landing/Hero.js | 4 ++-- src/components/landing/ReleaseCadence.js | 16 ++++++++-------- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/components/HomepageBanner.js b/src/components/HomepageBanner.js index 9822598..a0de3c2 100644 --- a/src/components/HomepageBanner.js +++ b/src/components/HomepageBanner.js @@ -8,17 +8,17 @@ export default function HomepageBanner() {

- New in v1.12: channel storage and live user metadata updates! + New in v1.13: message reactions and presence pub/sub! - New in v1.12: persistent channel storage with real-time sync and - live user metadata updates without reconnecting! + New in v1.13: message reactions, presence events on pub/sub, + and server-enforced heartbeats!

Learn more diff --git a/src/components/landing/Capabilities.js b/src/components/landing/Capabilities.js index e943a2e..7a84319 100644 --- a/src/components/landing/Capabilities.js +++ b/src/components/landing/Capabilities.js @@ -10,13 +10,28 @@ const CAPABILITIES = [ href: "/docs/channels/presence/", icon: "◉", }, + { + title: "Message reactions", + description: + "Add and remove reactions on stored messages with real-time fan-out and counts in history.", + href: "/docs/connections/claims/#channels.messages.react", + icon: "♥", + badge: "New", + }, + { + title: "Server-enforced heartbeats", + description: + "Set a heartbeat timeout in the token and Hotsock disconnects clients that go silent.", + href: "/docs/connections/claims/#heartbeatTimeout", + icon: "♡", + badge: "New", + }, { title: "Channel storage", description: "Persistent per-key state delivered to subscribers on join, with independent TTLs.", href: "/docs/channels/storage/", icon: "▣", - badge: "New", }, { title: "Live metadata updates", @@ -24,7 +39,6 @@ const CAPABILITIES = [ "Change a connection's user metadata on the fly. No reconnecting, no resubscribing.", href: "/docs/connections/claims/#umdUpdate", icon: "↻", - badge: "New", }, { title: "Auto-subscribe on connect", @@ -64,7 +78,7 @@ const CAPABILITIES = [ { title: "Pub/sub to SNS & EventBridge", description: - "Fan connection, message, and storage events out to the rest of your AWS workload.", + "Fan connection, message, presence, and storage events out to the rest of your AWS workload.", href: "/docs/server-api/events/", icon: "☷", }, diff --git a/src/components/landing/Hero.js b/src/components/landing/Hero.js index e06b598..a5eeecc 100644 --- a/src/components/landing/Hero.js +++ b/src/components/landing/Hero.js @@ -18,11 +18,11 @@ function Hero() {
- Now available · v1.12 channel storage & live user metadata + Now available · v1.13 message reactions & presence pub/sub

diff --git a/src/components/landing/ReleaseCadence.js b/src/components/landing/ReleaseCadence.js index b77cd67..2927d55 100644 --- a/src/components/landing/ReleaseCadence.js +++ b/src/components/landing/ReleaseCadence.js @@ -4,6 +4,14 @@ import Wrapper from "../global/Wrapper" import Arrow from "../../icons/arrow" const RELEASES = [ + { + version: "v1.13", + date: "May 2026", + title: "Message reactions & presence pub/sub", + summary: + "Add and remove reactions on stored messages, presence events on SNS/EventBridge, and server-enforced heartbeats.", + href: "/blog/message-reactions-and-presence-pubsub", + }, { version: "v1.12", date: "April 2026", @@ -44,14 +52,6 @@ const RELEASES = [ "Query stored messages by channel and event, with connection/source metadata attached.", href: "/docs/installation/changelog/#v1.8.0", }, - { - version: "v1.7", - date: "September 2025", - title: "Auto-subscribe & broadcast", - summary: - "Channels subscribed the moment you connect, and publishing to channels you're not on.", - href: "/blog/auto-subscribe-and-broadcast-messaging", - }, ] function ReleaseCadence() { From dbd7c145288f9e16076c0298f5c99c19258228f2 Mon Sep 17 00:00:00 2001 From: James Miller Date: Mon, 18 May 2026 13:47:35 -0700 Subject: [PATCH 11/34] remove inaccurate reaction pub/sub event docs reactions don't emit hotsock.messageReactionAdded/Removed events to SNS/EventBridge. they go through the standard hotsock.messagePublished path with event=hotsock.messageReaction in the metadata, and are gated by emitPubSubEvent like any other message (always true on the websocket path, customer-controlled on the http path). --- blog/2026-05-18-new-in-v1.13.mdx | 4 -- docs/installation/changelog.mdx | 1 - docs/server-api/events.mdx | 96 -------------------------------- 3 files changed, 101 deletions(-) diff --git a/blog/2026-05-18-new-in-v1.13.mdx b/blog/2026-05-18-new-in-v1.13.mdx index 5eea56c..51b62b3 100644 --- a/blog/2026-05-18-new-in-v1.13.mdx +++ b/blog/2026-05-18-new-in-v1.13.mdx @@ -92,10 +92,6 @@ Pass [`expandReactions: true`](/docs/connections/client-http-api/#connection/lis You can also fetch reactions for a single message with the dedicated [`connection/listReactions`](/docs/connections/client-http-api/#connection/listReactions) endpoint. -#### Pub/sub events for reactions {/* #pubsub-events-for-reactions */} - -[`hotsock.messageReactionAdded`](/docs/server-api/events/#hotsock.messageReactionAdded) and [`hotsock.messageReactionRemoved`](/docs/server-api/events/#hotsock.messageReactionRemoved) events are always emitted to SNS/EventBridge when reactions are added or removed, with no [`emitPubSubEvent`](/docs/connections/claims/#channels.messages.emitPubSubEvent) directive needed. This makes server-side aggregation, moderation, or analytics straightforward. - ### Presence member events on pub/sub (SNS/EventBridge) {/* #presence-member-pubsub */} Presence channel member lifecycle events ([`hotsock.memberAdded`](/docs/server-api/events/#hotsock.memberAdded), [`hotsock.memberRemoved`](/docs/server-api/events/#hotsock.memberRemoved), [`hotsock.memberUpdated`](/docs/server-api/events/#hotsock.memberUpdated)) are now also emitted to SNS/EventBridge, gated by the existing [`PublishEventsToSNSParameter` / `PublishEventsToEventBridgeParameter`](/docs/server-api/events/#eventbridge-sns-enable-disable) settings. The pub/sub events are deduplicated by [`uid`](/docs/connections/claims/#uid) to match WebSocket fan-out semantics, so joining from a second device when you're already present doesn't trigger another `memberAdded`. diff --git a/docs/installation/changelog.mdx b/docs/installation/changelog.mdx index b6080a1..ee0a76d 100644 --- a/docs/installation/changelog.mdx +++ b/docs/installation/changelog.mdx @@ -5,7 +5,6 @@ - Add [message reactions](../connections/claims.mdx#channels.messages.react). Clients send `hotsock.messageReaction` events with `add` or `remove` actions, gated by a new [`react`](../connections/claims.mdx#channels.messages.react) directive on channel message event patterns. Reactions require an active channel subscription, a [`uid`](../connections/claims.mdx#uid), and a stored target message that hasn't expired. Each user can have one reaction per reaction value per message. Also available via the [Client HTTP API](../connections/client-http-api.mdx) and the server-side [Lambda](../server-api/publish-messages.mdx#publish-with-lambda) and [HTTP URL](../server-api/publish-messages.mdx#publish-with-http-url) publish APIs. - Reaction counts are automatically included in [`listMessages`](../connections/client-http-api.mdx#connection/listMessages) responses on each message that has reactions. Pass [`expandReactions: true`](../connections/client-http-api.mdx#connection/listMessages.expandReactions) to also receive a list of reacting users per reaction value. - Add [`connection/listReactions`](../connections/client-http-api.mdx#connection/listReactions) Client HTTP API endpoint to list individual reactions on a single message with user attribution. -- Add [`hotsock.messageReactionAdded`](../server-api/events.mdx#hotsock.messageReactionAdded) and [`hotsock.messageReactionRemoved`](../server-api/events.mdx#hotsock.messageReactionRemoved) pub/sub events to SNS/EventBridge when reactions are added or removed. Always emitted — no `emitPubSubEvent` directive is required. - Add configurable [`limit`](../connections/client-http-api.mdx#connection/listMessages.limit) parameter to [`connection/listMessages`](../connections/client-http-api.mdx#connection/listMessages) (default 100). - Publish [`hotsock.memberAdded`](../server-api/events.mdx#hotsock.memberAdded), [`hotsock.memberRemoved`](../server-api/events.mdx#hotsock.memberRemoved), and [`hotsock.memberUpdated`](../server-api/events.mdx#hotsock.memberUpdated) presence channel events to SNS/EventBridge, deduplicated by `uid` to match WebSocket fan-out semantics. - Add [`heartbeatTimeout`](../connections/claims.mdx#heartbeatTimeout) connect-token claim for server-enforced connection heartbeats. When set (5–600 seconds), the client must send a [`hotsock.heartbeat`](../connections/keep-alive.mdx#send-hotsockheartbeat) message at least that often or the server will forcefully disconnect. Useful for [presence channels](../channels/presence.mdx) and other applications that need accurate dropped-connection detection beyond what API Gateway's idle timeout provides. diff --git a/docs/server-api/events.mdx b/docs/server-api/events.mdx index 213f267..2cb4521 100644 --- a/docs/server-api/events.mdx +++ b/docs/server-api/events.mdx @@ -507,102 +507,6 @@ SNS filter attributes are limited to `channel`, `key`, and `trigger`. The data object type for this event is `channelStorage`. -## `hotsock.messageReactionAdded` {/* #hotsock.messageReactionAdded */} - -This event is sent whenever a reaction is added to a stored message. Reaction events are always emitted to SNS/EventBridge — no `emitPubSubEvent` directive is required. - -### `type` {/* #hotsock.messageReactionAdded--type */} - -This is always `hotsock.messageReactionAdded`. - -### `metadata` {/* #hotsock.messageReactionAdded--metadata */} - -- `channel` (String): The name of the channel where the reaction was added. -- `connectionId` (String): The identifier for the connection that added the reaction. This field is only present for reactions initiated by a WebSocket client or Client HTTP API. -- `event` (String): This is always `hotsock.messageReaction`. -- `hotsockVersion` (String): The version of the Hotsock installation that generated this event. -- `requestId` (String): The request ID for the Lambda invocation that originally accepted the message. -- `sourceIp` (String): The IP address of the client that added the reaction. This field is only present if an IP address is known. -- `trigger` (String): The kind of initiator, set to one of `client.websocket`, `client.http`, `server.lambda`, or `server.http`. -- `userAgent` (String): The User-Agent of the client that added the reaction. This field is only present if a user agent is known. - -```json -{ - "channel": "my-channel", - "connectionId": "IDnAdd9kIAMCEsQ=", - "event": "hotsock.messageReaction", - "hotsockVersion": "1.13.0", - "requestId": "9d1d380a-0152-4962-bbbb-6e721b81db52", - "sourceIp": "12.34.56.78", - "trigger": "client.websocket", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" -} -``` - -### `data` {/* #hotsock.messageReactionAdded--data */} - -- `channel` (String): The name of the channel where the reaction was added. -- `event` (String): This is always `hotsock.messageReactionAdded`. -- `id` (String): The unique ID of this reaction event ([ULID](https://github.com/ulid/spec)). -- `data` (Object): The reaction payload, containing `messageId`, `reaction`, and `action`. -- `meta` (Object): Metadata about the user who added the reaction, including `uid` and `umd`. - -```json -{ - "channel": "my-channel", - "event": "hotsock.messageReactionAdded", - "id": "01JB4K7M2NC8QBYMZRBMCEXGNW", - "data": { - "messageId": "01JA3S0TNEC2QBYMZRBMCEXGNW", - "reaction": "thumbsup", - "action": "add" - }, - "meta": { "uid": "Jim", "umd": null } -} -``` - -### `dataType` {/* #hotsock.messageReactionAdded--dataType */} - -The data object type for this event is `message`. - -## `hotsock.messageReactionRemoved` {/* #hotsock.messageReactionRemoved */} - -This event is sent whenever a reaction is removed from a stored message. Reaction events are always emitted to SNS/EventBridge — no `emitPubSubEvent` directive is required. - -### `type` {/* #hotsock.messageReactionRemoved--type */} - -This is always `hotsock.messageReactionRemoved`. - -### `metadata` {/* #hotsock.messageReactionRemoved--metadata */} - -Same as [`hotsock.messageReactionAdded` metadata](#hotsock.messageReactionAdded--metadata). - -### `data` {/* #hotsock.messageReactionRemoved--data */} - -- `channel` (String): The name of the channel where the reaction was removed. -- `event` (String): This is always `hotsock.messageReactionRemoved`. -- `id` (String): The unique ID of this reaction event ([ULID](https://github.com/ulid/spec)). -- `data` (Object): The reaction payload, containing `messageId`, `reaction`, and `action`. -- `meta` (Object): Metadata about the user who removed the reaction, including `uid` and `umd`. - -```json -{ - "channel": "my-channel", - "event": "hotsock.messageReactionRemoved", - "id": "01JB4K8P3QD9RCZMASCNDFY0HX", - "data": { - "messageId": "01JA3S0TNEC2QBYMZRBMCEXGNW", - "reaction": "thumbsup", - "action": "remove" - }, - "meta": { "uid": "Jim", "umd": null } -} -``` - -### `dataType` {/* #hotsock.messageReactionRemoved--dataType */} - -The data object type for this event is `message`. - ## `hotsock.memberAdded` {/* #hotsock.memberAdded */} This event is sent whenever a unique member is added to a [presence channel](../channels/presence.mdx). Member events are deduplicated by `uid` to match WebSocket fan-out semantics — if a user is already present on the channel from another connection, no `hotsock.memberAdded` event fires when an additional connection joins. From 6bcf32c89242700fbe968e1d58ca1a4a269b98b4 Mon Sep 17 00:00:00 2001 From: James Miller Date: Mon, 18 May 2026 14:01:58 -0700 Subject: [PATCH 12/34] mark message reactions as preview --- blog/2026-05-18-new-in-v1.13.mdx | 10 +++++++--- docs/connections/claims.mdx | 4 ++++ docs/connections/client-http-api.mdx | 6 +++++- docs/installation/changelog.mdx | 6 +++--- src/components/landing/Capabilities.js | 2 +- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/blog/2026-05-18-new-in-v1.13.mdx b/blog/2026-05-18-new-in-v1.13.mdx index 51b62b3..e99fcfd 100644 --- a/blog/2026-05-18-new-in-v1.13.mdx +++ b/blog/2026-05-18-new-in-v1.13.mdx @@ -3,14 +3,18 @@ slug: message-reactions-and-presence-pubsub title: Message reactions, presence pub/sub, and heartbeat enforcement authors: [james] tags: [releases, features, channels, presence] -description: Hotsock v1.13 adds message reactions with per-event permissions and real-time delivery, presence member events to SNS/EventBridge, and a heartbeatTimeout claim for server-enforced connection liveness. +description: Hotsock v1.13 adds message reactions (preview) with per-event permissions and real-time delivery, presence member events to SNS/EventBridge, and a heartbeatTimeout claim for server-enforced connection liveness. --- -Hotsock v1.13 introduces **message reactions**, **presence member events on pub/sub (SNS/EventBridge)**, and a **`heartbeatTimeout` claim** for server-enforced connection liveness. +Hotsock v1.13 introduces **message reactions (preview)**, **presence member events on pub/sub (SNS/EventBridge)**, and a **`heartbeatTimeout` claim** for server-enforced connection liveness. {/* truncate */} -### Message reactions {/* #message-reactions */} +### Message reactions (preview) {/* #message-reactions */} + +:::info +Message reactions are available in preview as of v1.13.2. The feature is functional end to end, but behavior and API shape may change before GA. Try it out and [send feedback](https://github.com/hotsock/hotsock/discussions). +::: Clients can now add and remove reactions on stored messages, with real-time delivery to all channel subscribers, aggregated counts in message history, and a separate endpoint to list who reacted with what. diff --git a/docs/connections/claims.mdx b/docs/connections/claims.mdx index e8e6f77..61e1b23 100644 --- a/docs/connections/claims.mdx +++ b/docs/connections/claims.mdx @@ -407,6 +407,10 @@ In the following example, since `chat.*` includes `chat.admin`, this connection #### `react` {/* #channels.messages.react */} +:::info +Message reactions are available in preview as of v1.13.2. Behavior and API shape may change before GA. +::: + `Boolean` (optional) - Controls whether this connection can add or remove reactions on stored messages that match this event pattern. Reactions require an active channel subscription and a [`uid`](#uid) on the connection. Default is `false`. When a reaction is added or removed, all channel subscribers receive a `hotsock.messageReactionAdded` or `hotsock.messageReactionRemoved` message in real-time. Reaction counts are also included in [`listMessages`](./client-http-api.mdx#connection/listMessages) responses, and individual reactions can be listed with [`listReactions`](./client-http-api.mdx#connection/listReactions). diff --git a/docs/connections/client-http-api.mdx b/docs/connections/client-http-api.mdx index 40aee6f..af02207 100644 --- a/docs/connections/client-http-api.mdx +++ b/docs/connections/client-http-api.mdx @@ -59,7 +59,7 @@ When issuing a connect or subscribe token, specify the [`historyStart`](./claims This endpoint will return up to `limit` messages (default 100) or up to 1MB of data, whichever comes first. Fetch subsequent pages using `before` or `after`, depending on your use case. -If any returned messages have reactions, a `reactions` object is included on each message, keyed by reaction value with `{count, items?}` per entry. By default only `count` is set. Pass [`expandReactions: true`](#connection/listMessages.expandReactions) to also populate `items` with one entry per reacting user. +If any returned messages have reactions, a `reactions` object is included on each message, keyed by reaction value with `{count, items?}` per entry. By default only `count` is set. Pass [`expandReactions: true`](#connection/listMessages.expandReactions) to also populate `items` with one entry per reacting user. (Reactions are available in preview as of v1.13.2 — behavior and API shape may change before GA.) #### `after` {/* #connection/listMessages.after */} @@ -205,6 +205,10 @@ With `expandReactions: true`, each entry also has an `items` array with one elem ## `connection/listReactions` {/* #connection/listReactions */} +:::info +Message reactions are available in preview as of v1.13.2. Behavior and API shape may change before GA. +::: + List the individual reactions on a specific stored message, including who reacted. The connection must be subscribed to the channel. #### `channel` {/* #connection/listReactions.channel */} diff --git a/docs/installation/changelog.mdx b/docs/installation/changelog.mdx index ee0a76d..563b095 100644 --- a/docs/installation/changelog.mdx +++ b/docs/installation/changelog.mdx @@ -2,9 +2,9 @@ ## v1.13.2 - May 18, 2026 {/* #v1.13.2 */} -- Add [message reactions](../connections/claims.mdx#channels.messages.react). Clients send `hotsock.messageReaction` events with `add` or `remove` actions, gated by a new [`react`](../connections/claims.mdx#channels.messages.react) directive on channel message event patterns. Reactions require an active channel subscription, a [`uid`](../connections/claims.mdx#uid), and a stored target message that hasn't expired. Each user can have one reaction per reaction value per message. Also available via the [Client HTTP API](../connections/client-http-api.mdx) and the server-side [Lambda](../server-api/publish-messages.mdx#publish-with-lambda) and [HTTP URL](../server-api/publish-messages.mdx#publish-with-http-url) publish APIs. -- Reaction counts are automatically included in [`listMessages`](../connections/client-http-api.mdx#connection/listMessages) responses on each message that has reactions. Pass [`expandReactions: true`](../connections/client-http-api.mdx#connection/listMessages.expandReactions) to also receive a list of reacting users per reaction value. -- Add [`connection/listReactions`](../connections/client-http-api.mdx#connection/listReactions) Client HTTP API endpoint to list individual reactions on a single message with user attribution. +- Add [message reactions](../connections/claims.mdx#channels.messages.react) (**preview**). Clients send `hotsock.messageReaction` events with `add` or `remove` actions, gated by a new [`react`](../connections/claims.mdx#channels.messages.react) directive on channel message event patterns. Reactions require an active channel subscription, a [`uid`](../connections/claims.mdx#uid), and a stored target message that hasn't expired. Each user can have one reaction per reaction value per message. Also available via the [Client HTTP API](../connections/client-http-api.mdx) and the server-side [Lambda](../server-api/publish-messages.mdx#publish-with-lambda) and [HTTP URL](../server-api/publish-messages.mdx#publish-with-http-url) publish APIs. Behavior and API shape may change before GA. +- Reaction counts are automatically included in [`listMessages`](../connections/client-http-api.mdx#connection/listMessages) responses on each message that has reactions. Pass [`expandReactions: true`](../connections/client-http-api.mdx#connection/listMessages.expandReactions) to also receive a list of reacting users per reaction value. (Part of the reactions preview.) +- Add [`connection/listReactions`](../connections/client-http-api.mdx#connection/listReactions) Client HTTP API endpoint to list individual reactions on a single message with user attribution. (Part of the reactions preview.) - Add configurable [`limit`](../connections/client-http-api.mdx#connection/listMessages.limit) parameter to [`connection/listMessages`](../connections/client-http-api.mdx#connection/listMessages) (default 100). - Publish [`hotsock.memberAdded`](../server-api/events.mdx#hotsock.memberAdded), [`hotsock.memberRemoved`](../server-api/events.mdx#hotsock.memberRemoved), and [`hotsock.memberUpdated`](../server-api/events.mdx#hotsock.memberUpdated) presence channel events to SNS/EventBridge, deduplicated by `uid` to match WebSocket fan-out semantics. - Add [`heartbeatTimeout`](../connections/claims.mdx#heartbeatTimeout) connect-token claim for server-enforced connection heartbeats. When set (5–600 seconds), the client must send a [`hotsock.heartbeat`](../connections/keep-alive.mdx#send-hotsockheartbeat) message at least that often or the server will forcefully disconnect. Useful for [presence channels](../channels/presence.mdx) and other applications that need accurate dropped-connection detection beyond what API Gateway's idle timeout provides. diff --git a/src/components/landing/Capabilities.js b/src/components/landing/Capabilities.js index 7a84319..c3f5558 100644 --- a/src/components/landing/Capabilities.js +++ b/src/components/landing/Capabilities.js @@ -16,7 +16,7 @@ const CAPABILITIES = [ "Add and remove reactions on stored messages with real-time fan-out and counts in history.", href: "/docs/connections/claims/#channels.messages.react", icon: "♥", - badge: "New", + badge: "Preview", }, { title: "Server-enforced heartbeats", From feebc72b0113f77fe3915af528586c60c0b1ad73 Mon Sep 17 00:00:00 2001 From: James Miller Date: Mon, 18 May 2026 14:06:14 -0700 Subject: [PATCH 13/34] drop message reactions from v1.13 release notes reactions are still in the v1.13.2 binary but need more time to bake before they're publicly documented. all docs, changelog entries, blog post, and homepage references removed for now. --- blog/2026-05-18-new-in-v1.13.mdx | 96 ++------------------- docs/connections/claims.mdx | 59 +------------ docs/connections/client-http-api.mdx | 102 +---------------------- docs/installation/changelog.mdx | 14 +--- src/components/HomepageBanner.js | 8 +- src/components/landing/Capabilities.js | 8 -- src/components/landing/Hero.js | 4 +- src/components/landing/ReleaseCadence.js | 6 +- 8 files changed, 18 insertions(+), 279 deletions(-) diff --git a/blog/2026-05-18-new-in-v1.13.mdx b/blog/2026-05-18-new-in-v1.13.mdx index e99fcfd..0eb7c06 100644 --- a/blog/2026-05-18-new-in-v1.13.mdx +++ b/blog/2026-05-18-new-in-v1.13.mdx @@ -1,101 +1,15 @@ --- -slug: message-reactions-and-presence-pubsub -title: Message reactions, presence pub/sub, and heartbeat enforcement +slug: presence-pubsub-and-heartbeats +title: Presence pub/sub and heartbeat enforcement authors: [james] -tags: [releases, features, channels, presence] -description: Hotsock v1.13 adds message reactions (preview) with per-event permissions and real-time delivery, presence member events to SNS/EventBridge, and a heartbeatTimeout claim for server-enforced connection liveness. +tags: [releases, features, presence] +description: Hotsock v1.13 publishes presence member events to SNS/EventBridge and adds a heartbeatTimeout claim for server-enforced connection liveness. --- -Hotsock v1.13 introduces **message reactions (preview)**, **presence member events on pub/sub (SNS/EventBridge)**, and a **`heartbeatTimeout` claim** for server-enforced connection liveness. +Hotsock v1.13 publishes **presence member events to pub/sub (SNS/EventBridge)** and adds a **`heartbeatTimeout` claim** for server-enforced connection liveness. {/* truncate */} -### Message reactions (preview) {/* #message-reactions */} - -:::info -Message reactions are available in preview as of v1.13.2. The feature is functional end to end, but behavior and API shape may change before GA. Try it out and [send feedback](https://github.com/hotsock/hotsock/discussions). -::: - -Clients can now add and remove reactions on stored messages, with real-time delivery to all channel subscribers, aggregated counts in message history, and a separate endpoint to list who reacted with what. - -Reactions are gated by a new [`react`](/docs/connections/claims/#channels.messages.react) directive on channel message event patterns, alongside the existing [`publish`](/docs/connections/claims/#channels.messages.publish), [`store`](/docs/connections/claims/#channels.messages.store), [`echo`](/docs/connections/claims/#channels.messages.echo), and other directives. A connection needs three things to react: an active channel subscription, a [`uid`](/docs/connections/claims/#uid), and a stored target message that hasn't expired. - -#### Setting up permissions {/* #setting-up-permissions */} - -```json -{ - "exp": 1747574400, - "scope": "connect", - "uid": "Jim", - "channels": { - "room.123": { - "subscribe": true, - "messages": { - "chat": { - "publish": true, - "echo": true, - "store": 31536000, - // highlight-next-line - "react": true - } - } - } - } -} -``` - -#### Adding and removing reactions {/* #adding-and-removing-reactions */} - -Clients send a `hotsock.messageReaction` message on the WebSocket with the target message ID, the reaction value, and an explicit `add` or `remove` action: - -``` -> {"event":"hotsock.messageReaction", "channel":"room.123", "data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW", "reaction":"thumbsup", "action":"add"}} -``` - -All channel subscribers immediately receive a `hotsock.messageReactionAdded` event: - -``` -< {"id":"01JB4K7M2N...","event":"hotsock.messageReactionAdded","channel":"room.123","data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW","reaction":"thumbsup","action":"add"},"meta":{"uid":"Jim","umd":null}} -``` - -Reaction data can be emoji characters, Slack-style colon-wrapped strings like `:thumbsup:`, or any short string up to 100 bytes. Each user can have one reaction per reaction value per message, and adding the same reaction again is a no-op. - -#### Reactions in message history {/* #reactions-in-message-history */} - -[`listMessages`](/docs/connections/client-http-api/#connection/listMessages) responses now include a `reactions` object on each message that has reactions, keyed by reaction value: - -```json -{ - "messages": [ - { - "id": "01JA3S0P7FB7WVYS67X316M32S", - "event": "chat", - "channel": "room.123", - "data": "Wow. Can we make it a different moment?", - "meta": { "uid": "Jim", "umd": null }, - // highlight-next-line - "reactions": { "thumbsup": { "count": 2 }, "heart": { "count": 1 } } - } - ] -} -``` - -Pass [`expandReactions: true`](/docs/connections/client-http-api/#connection/listMessages.expandReactions) to also include an `items` array per reaction with the user behind each one: - -```json -"reactions": { - "thumbsup": { - "count": 2, - "items": [ - { "uid": "Jim", "umd": { "name": "Jim Halpert" } }, - { "uid": "Pam" } - ] - } -} -``` - -You can also fetch reactions for a single message with the dedicated [`connection/listReactions`](/docs/connections/client-http-api/#connection/listReactions) endpoint. - ### Presence member events on pub/sub (SNS/EventBridge) {/* #presence-member-pubsub */} Presence channel member lifecycle events ([`hotsock.memberAdded`](/docs/server-api/events/#hotsock.memberAdded), [`hotsock.memberRemoved`](/docs/server-api/events/#hotsock.memberRemoved), [`hotsock.memberUpdated`](/docs/server-api/events/#hotsock.memberUpdated)) are now also emitted to SNS/EventBridge, gated by the existing [`PublishEventsToSNSParameter` / `PublishEventsToEventBridgeParameter`](/docs/server-api/events/#eventbridge-sns-enable-disable) settings. The pub/sub events are deduplicated by [`uid`](/docs/connections/claims/#uid) to match WebSocket fan-out semantics, so joining from a second device when you're already present doesn't trigger another `memberAdded`. diff --git a/docs/connections/claims.mdx b/docs/connections/claims.mdx index 61e1b23..704d18f 100644 --- a/docs/connections/claims.mdx +++ b/docs/connections/claims.mdx @@ -226,7 +226,7 @@ If you wanted to allow access to the entire message history of a channel without `Object` (optional) - Manages the permissions and directives for client-initiated messages that are published directly to the WebSocket on the channel(s) for the specified events. Each object key is the name of an event and can include asterisks (\*) anywhere in the string to denote wildcards. Keys can also use the `#regex:` prefix to specify a regular expression pattern for matching event names. Each object value is another object with the settings for that event or event pattern. -Each object inside the messages object accepts [`broadcast`](#channels.messages.broadcast), [`echo`](#channels.messages.echo), [`emitPubSubEvent`](#channels.messages.emitPubSubEvent), [`publish`](#channels.messages.publish), [`react`](#channels.messages.react), [`scheduleBefore`](#channels.messages.scheduleBefore), and [`store`](#channels.messages.store) attributes. +Each object inside the messages object accepts [`broadcast`](#channels.messages.broadcast), [`echo`](#channels.messages.echo), [`emitPubSubEvent`](#channels.messages.emitPubSubEvent), [`publish`](#channels.messages.publish), [`scheduleBefore`](#channels.messages.scheduleBefore), and [`store`](#channels.messages.store) attributes. #### Regex patterns {/* #channels.messages--regex */} @@ -405,63 +405,6 @@ In the following example, since `chat.*` includes `chat.admin`, this connection ::: -#### `react` {/* #channels.messages.react */} - -:::info -Message reactions are available in preview as of v1.13.2. Behavior and API shape may change before GA. -::: - -`Boolean` (optional) - Controls whether this connection can add or remove reactions on stored messages that match this event pattern. Reactions require an active channel subscription and a [`uid`](#uid) on the connection. Default is `false`. - -When a reaction is added or removed, all channel subscribers receive a `hotsock.messageReactionAdded` or `hotsock.messageReactionRemoved` message in real-time. Reaction counts are also included in [`listMessages`](./client-http-api.mdx#connection/listMessages) responses, and individual reactions can be listed with [`listReactions`](./client-http-api.mdx#connection/listReactions). - -The following allows reactions on `chat` messages in the "mychannel" channel. - -```json -{ - "channels": { - "mychannel": { - "subscribe": true, - "messages": { - "chat": { - "publish": true, - "echo": true, - "store": 31536000, - // highlight-next-line - "react": true - } - } - } - } -} -``` - -To add a reaction, send a `hotsock.messageReaction` message on the WebSocket: - -``` -> {"event":"hotsock.messageReaction", "channel":"mychannel", "data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW", "reaction":"thumbsup", "action":"add"}} -``` - -To remove a reaction: - -``` -> {"event":"hotsock.messageReaction", "channel":"mychannel", "data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW", "reaction":"thumbsup", "action":"remove"}} -``` - -All channel subscribers receive the reaction event: - -``` -< {"id":"01JB4K7M2N...","event":"hotsock.messageReactionAdded","channel":"mychannel","data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW","reaction":"thumbsup","action":"add"},"meta":{"uid":"Jim","umd":null}} -``` - -Reaction data must not be blank, must be no more than 100 bytes, and must not contain number signs (`#`). Reactions can be emoji characters, Slack-style colon-wrapped strings like `:thumbsup:`, or any other short string. - -:::info -Reactions can only be added to stored messages (messages published with a [`store`](#channels.messages.store) TTL). The target message must exist and not be expired. Each user can have one reaction per reaction value per message — adding the same reaction again is a no-op. -::: - -Also available via the Client HTTP API at [`connection/publishMessage`](./client-http-api.mdx#connection/publishMessage) using `"event": "hotsock.messageReaction"` with the reaction payload as `data`, or via the server-side [Lambda](../server-api/publish-messages.mdx#publish-with-lambda) and [HTTP URL](../server-api/publish-messages.mdx#publish-with-http-url) publish APIs. - #### `scheduleBefore` {/* #channels.messages.scheduleBefore */} `NumericDate` (optional) - If [client-initiated message publishing](#channels.messages.publish) is permitted for this channel and event, the `scheduleBefore` attribute specifies the furthest out future time that this connection can schedule messages for delivery. If supplied, the schedule before time must be expressed as a Unix timestamp - the number of seconds since the Unix epoch. By default, or if `scheduleBefore` is provided as `0` or any timestamp in the past, scheduled message publishing is not permitted. diff --git a/docs/connections/client-http-api.mdx b/docs/connections/client-http-api.mdx index af02207..e131b8f 100644 --- a/docs/connections/client-http-api.mdx +++ b/docs/connections/client-http-api.mdx @@ -5,7 +5,7 @@ toc_max_heading_level: 4 # Client HTTP API -In addition to WebSocket interactions, connected clients can also make use of an HTTP API for publishing messages, listing message history, listing reactions, reading and writing channel storage, and updating user metadata. All requests **_must_ be `POST` requests** and **_must_ have URL-encoded `connectionId` and `connectionSecret`** query parameters set. +In addition to WebSocket interactions, connected clients can also make use of an HTTP API for publishing messages, listing message history, reading and writing channel storage, and updating user metadata. All requests **_must_ be `POST` requests** and **_must_ have URL-encoded `connectionId` and `connectionSecret`** query parameters set. ## Determine your API URL {/* #determine-your-api-url */} @@ -59,8 +59,6 @@ When issuing a connect or subscribe token, specify the [`historyStart`](./claims This endpoint will return up to `limit` messages (default 100) or up to 1MB of data, whichever comes first. Fetch subsequent pages using `before` or `after`, depending on your use case. -If any returned messages have reactions, a `reactions` object is included on each message, keyed by reaction value with `{count, items?}` per entry. By default only `count` is set. Pass [`expandReactions: true`](#connection/listMessages.expandReactions) to also populate `items` with one entry per reacting user. (Reactions are available in preview as of v1.13.2 — behavior and API shape may change before GA.) - #### `after` {/* #connection/listMessages.after */} String (optional) - Load messages after (newer than) the message with the ID specified here. Has no effect if `reverse` is `true`. @@ -81,10 +79,6 @@ Example: `01JA3HVNF6E89HDT1ABRFWJ8C8` String (required) - The name of the channel to query message history. -#### `expandReactions` {/* #connection/listMessages.expandReactions */} - -Boolean (optional) - If `true`, the `reactions` object on each returned message is enriched with an `items` array per reaction containing the `uid` and `umd` of each reacting user. Default is `false`, which returns aggregated counts only. Messages with no reactions emit `{}` when `expandReactions` is `true` and omit the field otherwise. - #### `limit` {/* #connection/listMessages.limit */} Integer (optional) - The maximum number of messages to return. Default is `100`. @@ -132,9 +126,7 @@ fetch( "meta": { "uid": "Jim", "umd": null - }, - // highlight-next-line - "reactions": { "thumbsup": { "count": 2 }, "heart": { "count": 1 } } + } }, { "id": "01JA3S0GB6S3WNTV2S1RJ421TH", @@ -164,97 +156,7 @@ fetch( "meta": { "uid": "Pam", "umd": null - }, - // highlight-next-line - "reactions": { "heart": { "count": 3 } } - } - ] -} -``` - -With `expandReactions: true`, each entry also has an `items` array with one element per reacting user: - -```json -{ - "messages": [ - { - "id": "01JA3S0P7FB7WVYS67X316M32S", - "event": "my-event", - "channel": "my-channel", - "data": "Wow. Can we make it a different moment?", - "meta": { "uid": "Jim", "umd": null }, - // highlight-start - "reactions": { - "thumbsup": { - "count": 2, - "items": [ - { "uid": "Jim", "umd": { "name": "Jim Halpert" } }, - { "uid": "Pam" } - ] - }, - "heart": { - "count": 1, - "items": [{ "uid": "Dwight" }] - } } - // highlight-end - } - ] -} -``` - -## `connection/listReactions` {/* #connection/listReactions */} - -:::info -Message reactions are available in preview as of v1.13.2. Behavior and API shape may change before GA. -::: - -List the individual reactions on a specific stored message, including who reacted. The connection must be subscribed to the channel. - -#### `channel` {/* #connection/listReactions.channel */} - -`String` (required) - The name of the channel where the message was published. - -#### `messageId` {/* #connection/listReactions.messageId */} - -`String` (required) - The ID of the message to list reactions for. - -### Example {/* #connection/listReactions--example */} - -#### Request {/* #connection/listReactions--example-request */} - -```javascript -fetch( - "https://r6zcm2.lambda-url.us-east-1.on.aws/connection/listReactions?connectionId=fjlb_eHLIAMCKRg%3d&connectionSecret=SZy32Etv0KIbe4Jod6KH", - { - method: "POST", - body: JSON.stringify({ - channel: "my-channel", - messageId: "01JA3S0P7FB7WVYS67X316M32S", - }), - }, -) -``` - -#### Response {/* #connection/listReactions--example-response */} - -Expect a `200 OK` status code for successful reads. - -```json -{ - "reactions": [ - { - "reaction": "thumbsup", - "uid": "Jim", - "umd": { "name": "Jim Halpert" } - }, - { - "reaction": "thumbsup", - "uid": "Pam" - }, - { - "reaction": "heart", - "uid": "Dwight" } ] } diff --git a/docs/installation/changelog.mdx b/docs/installation/changelog.mdx index 563b095..efbbb2b 100644 --- a/docs/installation/changelog.mdx +++ b/docs/installation/changelog.mdx @@ -2,26 +2,14 @@ ## v1.13.2 - May 18, 2026 {/* #v1.13.2 */} -- Add [message reactions](../connections/claims.mdx#channels.messages.react) (**preview**). Clients send `hotsock.messageReaction` events with `add` or `remove` actions, gated by a new [`react`](../connections/claims.mdx#channels.messages.react) directive on channel message event patterns. Reactions require an active channel subscription, a [`uid`](../connections/claims.mdx#uid), and a stored target message that hasn't expired. Each user can have one reaction per reaction value per message. Also available via the [Client HTTP API](../connections/client-http-api.mdx) and the server-side [Lambda](../server-api/publish-messages.mdx#publish-with-lambda) and [HTTP URL](../server-api/publish-messages.mdx#publish-with-http-url) publish APIs. Behavior and API shape may change before GA. -- Reaction counts are automatically included in [`listMessages`](../connections/client-http-api.mdx#connection/listMessages) responses on each message that has reactions. Pass [`expandReactions: true`](../connections/client-http-api.mdx#connection/listMessages.expandReactions) to also receive a list of reacting users per reaction value. (Part of the reactions preview.) -- Add [`connection/listReactions`](../connections/client-http-api.mdx#connection/listReactions) Client HTTP API endpoint to list individual reactions on a single message with user attribution. (Part of the reactions preview.) -- Add configurable [`limit`](../connections/client-http-api.mdx#connection/listMessages.limit) parameter to [`connection/listMessages`](../connections/client-http-api.mdx#connection/listMessages) (default 100). - Publish [`hotsock.memberAdded`](../server-api/events.mdx#hotsock.memberAdded), [`hotsock.memberRemoved`](../server-api/events.mdx#hotsock.memberRemoved), and [`hotsock.memberUpdated`](../server-api/events.mdx#hotsock.memberUpdated) presence channel events to SNS/EventBridge, deduplicated by `uid` to match WebSocket fan-out semantics. - Add [`heartbeatTimeout`](../connections/claims.mdx#heartbeatTimeout) connect-token claim for server-enforced connection heartbeats. When set (5–600 seconds), the client must send a [`hotsock.heartbeat`](../connections/keep-alive.mdx#send-hotsockheartbeat) message at least that often or the server will forcefully disconnect. Useful for [presence channels](../channels/presence.mdx) and other applications that need accurate dropped-connection detection beyond what API Gateway's idle timeout provides. +- Add configurable [`limit`](../connections/client-http-api.mdx#connection/listMessages.limit) parameter to [`connection/listMessages`](../connections/client-http-api.mdx#connection/listMessages) (default 100). - Improve subscription cleanup throughput when many connections disconnect or unsubscribe at the same time. -- Tighten heartbeat-check scheduling so stale connections are disconnected closer to their actual deadline. - Update the [Web Console](../server-api/web-console.mdx) to the latest build. - Build with Go 1.26.3. - Update all aws-sdk-go-v2 SDK modules to their latest versions (as of [2026-05-12](https://github.com/aws/aws-sdk-go-v2/releases/tag/release-2026-05-12)). -## v1.13.1 - May 18, 2026 {/* #v1.13.1 */} - -- Skip this release. See [v1.13.2](#v1.13.2) for the full list of changes. - -## v1.13.0 - May 18, 2026 {/* #v1.13.0 */} - -- Skip this release. See [v1.13.2](#v1.13.2) for the full list of changes. - ## v1.12.0 - April 8, 2026 {/* #v1.12.0 */} - Add [channel storage](../connections/claims.mdx#channels.storage), a per-key persistent key-value store on channels. Storage permissions are configured per-key with wildcard and regex pattern support. Entries have independent TTLs, are delivered to [`observe`](../connections/claims.mdx#channels.storage.observe) subscribers on join, and skip fan-out when the value hasn't changed. Clients interact with storage via [`hotsock.channelStorageSet`](../connections/claims.mdx#channels.storage.set) / [`hotsock.channelStorageGet`](../connections/claims.mdx#channels.storage.get) on the WebSocket or the [`connection/channelStorageGet`](../connections/client-http-api.mdx#connection/channelStorageGet), [`connection/channelStorageSet`](../connections/client-http-api.mdx#connection/channelStorageSet), and [`connection/channelStorageList`](../connections/client-http-api.mdx#connection/channelStorageList) Client HTTP API endpoints. Storage operations do not require an active channel subscription. Server-side writes are available via the Lambda and HTTP publish APIs using `"event": "hotsock.channelStorageSet"` with a `key` field. diff --git a/src/components/HomepageBanner.js b/src/components/HomepageBanner.js index a0de3c2..ccf1f36 100644 --- a/src/components/HomepageBanner.js +++ b/src/components/HomepageBanner.js @@ -8,17 +8,17 @@ export default function HomepageBanner() {

- New in v1.13: message reactions and presence pub/sub! + New in v1.13: presence pub/sub and heartbeat enforcement! - New in v1.13: message reactions, presence events on pub/sub, - and server-enforced heartbeats! + New in v1.13: presence events on pub/sub and server-enforced + heartbeats!

Learn more diff --git a/src/components/landing/Capabilities.js b/src/components/landing/Capabilities.js index c3f5558..0b40d42 100644 --- a/src/components/landing/Capabilities.js +++ b/src/components/landing/Capabilities.js @@ -10,14 +10,6 @@ const CAPABILITIES = [ href: "/docs/channels/presence/", icon: "◉", }, - { - title: "Message reactions", - description: - "Add and remove reactions on stored messages with real-time fan-out and counts in history.", - href: "/docs/connections/claims/#channels.messages.react", - icon: "♥", - badge: "Preview", - }, { title: "Server-enforced heartbeats", description: diff --git a/src/components/landing/Hero.js b/src/components/landing/Hero.js index a5eeecc..f184d9f 100644 --- a/src/components/landing/Hero.js +++ b/src/components/landing/Hero.js @@ -18,11 +18,11 @@ function Hero() {
- Now available · v1.13 message reactions & presence pub/sub + Now available · v1.13 presence pub/sub & heartbeat enforcement

diff --git a/src/components/landing/ReleaseCadence.js b/src/components/landing/ReleaseCadence.js index 2927d55..79c3404 100644 --- a/src/components/landing/ReleaseCadence.js +++ b/src/components/landing/ReleaseCadence.js @@ -7,10 +7,10 @@ const RELEASES = [ { version: "v1.13", date: "May 2026", - title: "Message reactions & presence pub/sub", + title: "Presence pub/sub & heartbeat enforcement", summary: - "Add and remove reactions on stored messages, presence events on SNS/EventBridge, and server-enforced heartbeats.", - href: "/blog/message-reactions-and-presence-pubsub", + "Presence member events on SNS/EventBridge and server-enforced heartbeats for accurate dropped-connection detection.", + href: "/blog/presence-pubsub-and-heartbeats", }, { version: "v1.12", From 1552b665ff9aea92d9018aba510fe9df12d8ca31 Mon Sep 17 00:00:00 2001 From: James Miller Date: Mon, 18 May 2026 14:10:21 -0700 Subject: [PATCH 14/34] homepage: reorder capabilities to lead with private/presence, storage, history, pub/sub --- src/components/landing/Capabilities.js | 56 +++++++++++++------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/components/landing/Capabilities.js b/src/components/landing/Capabilities.js index 0b40d42..e1cef57 100644 --- a/src/components/landing/Capabilities.js +++ b/src/components/landing/Capabilities.js @@ -10,6 +10,27 @@ const CAPABILITIES = [ href: "/docs/channels/presence/", icon: "◉", }, + { + title: "Channel storage", + description: + "Persistent per-key state delivered to subscribers on join, with independent TTLs.", + href: "/docs/channels/storage/", + icon: "▣", + }, + { + title: "Message storage & history", + description: + "Retain messages per-event and query them by channel and event name, sorted by time.", + href: "/docs/connections/client-http-api/", + icon: "▦", + }, + { + title: "Pub/sub to SNS & EventBridge", + description: + "Fan connection, message, presence, and storage events out to the rest of your AWS workload.", + href: "/docs/server-api/events/", + icon: "☷", + }, { title: "Server-enforced heartbeats", description: @@ -18,13 +39,6 @@ const CAPABILITIES = [ icon: "♡", badge: "New", }, - { - title: "Channel storage", - description: - "Persistent per-key state delivered to subscribers on join, with independent TTLs.", - href: "/docs/channels/storage/", - icon: "▣", - }, { title: "Live metadata updates", description: @@ -32,6 +46,13 @@ const CAPABILITIES = [ href: "/docs/connections/claims/#umdUpdate", icon: "↻", }, + { + title: "Scheduled messages", + description: + "Publish a message now, deliver it later. Backed by EventBridge Scheduler.", + href: "/docs/server-api/publish-messages/#message-format.scheduleExpression", + icon: "⏱", + }, { title: "Auto-subscribe on connect", description: @@ -46,20 +67,6 @@ const CAPABILITIES = [ href: "/docs/connections/claims/#channels.alias", icon: "⇄", }, - { - title: "Scheduled messages", - description: - "Publish a message now, deliver it later. Backed by EventBridge Scheduler.", - href: "/docs/server-api/publish-messages/#message-format.scheduleExpression", - icon: "⏱", - }, - { - title: "Message storage & history", - description: - "Retain messages per-event and query them by channel and event name, sorted by time.", - href: "/docs/connections/client-http-api/", - icon: "▦", - }, { title: "Broadcast to unsubscribed", description: @@ -67,13 +74,6 @@ const CAPABILITIES = [ href: "/docs/connections/claims/#channels.messages.broadcast", icon: "⇡", }, - { - title: "Pub/sub to SNS & EventBridge", - description: - "Fan connection, message, presence, and storage events out to the rest of your AWS workload.", - href: "/docs/server-api/events/", - icon: "☷", - }, { title: "Regex & wildcard claims", description: From 4ece3d13867838c4af23126fddafca1d7d85ecf2 Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 19 May 2026 15:20:56 -0700 Subject: [PATCH 15/34] Add message reactions back to v1.13 release notes Reactions are ready. Key differences from the previous iteration: - react directive accepts true, false, or an array of allowed values - pub/sub events (hotsock.messageReactionAdded/Removed) are emitted from the DynamoDB stream processor post-commit, gated by emitPubSubEvent - TTL-driven reaction deletes don't emit messageReactionRemoved - pub/sub data shape: {channel, messageId, id, reaction, uid, umd, createdAt, expiresAt} with dataType "messageReaction" - metadata: {channel, reaction, trigger, connectionId?, sourceIp?, userAgent?} --- blog/2026-05-18-new-in-v1.13.mdx | 98 ++++++++++++++++++++++-- docs/connections/claims.mdx | 77 ++++++++++++++++++- docs/connections/client-http-api.mdx | 98 +++++++++++++++++++++++- docs/installation/changelog.mdx | 4 + docs/server-api/events.mdx | 91 ++++++++++++++++++++++ src/components/HomepageBanner.js | 8 +- src/components/landing/Capabilities.js | 8 ++ src/components/landing/Hero.js | 4 +- src/components/landing/ReleaseCadence.js | 6 +- 9 files changed, 377 insertions(+), 17 deletions(-) diff --git a/blog/2026-05-18-new-in-v1.13.mdx b/blog/2026-05-18-new-in-v1.13.mdx index 0eb7c06..49f109f 100644 --- a/blog/2026-05-18-new-in-v1.13.mdx +++ b/blog/2026-05-18-new-in-v1.13.mdx @@ -1,15 +1,103 @@ --- -slug: presence-pubsub-and-heartbeats -title: Presence pub/sub and heartbeat enforcement +slug: message-reactions-and-presence-pubsub +title: Message reactions, presence pub/sub, and heartbeat enforcement authors: [james] -tags: [releases, features, presence] -description: Hotsock v1.13 publishes presence member events to SNS/EventBridge and adds a heartbeatTimeout claim for server-enforced connection liveness. +tags: [releases, features, channels, presence] +description: Hotsock v1.13 adds message reactions with per-event permissions and real-time delivery, presence member events to SNS/EventBridge, and a heartbeatTimeout claim for server-enforced connection liveness. --- -Hotsock v1.13 publishes **presence member events to pub/sub (SNS/EventBridge)** and adds a **`heartbeatTimeout` claim** for server-enforced connection liveness. +Hotsock v1.13 introduces **message reactions**, **presence member events on pub/sub (SNS/EventBridge)**, and a **`heartbeatTimeout` claim** for server-enforced connection liveness. {/* truncate */} +### Message reactions {/* #message-reactions */} + +Clients can now add and remove reactions on stored messages, with real-time delivery to all channel subscribers, aggregated counts in message history, and a separate endpoint to list who reacted with what. + +Reactions are gated by a new [`react`](/docs/connections/claims/#channels.messages.react) directive on channel message event patterns, alongside the existing [`publish`](/docs/connections/claims/#channels.messages.publish), [`store`](/docs/connections/claims/#channels.messages.store), [`echo`](/docs/connections/claims/#channels.messages.echo), and other directives. A connection needs three things to react: an active channel subscription, a [`uid`](/docs/connections/claims/#uid), and a stored target message that hasn't expired. + +#### Setting up permissions {/* #setting-up-permissions */} + +The `react` directive accepts `true` (allow any reaction), `false` (deny), or an array of strings to restrict which reactions are permitted: + +```json +{ + "exp": 1747574400, + "scope": "connect", + "uid": "Jim", + "channels": { + "room.123": { + "subscribe": true, + "messages": { + "chat": { + "publish": true, + "echo": true, + "store": 31536000, + // highlight-next-line + "react": ["thumbsup", "heart", "laugh"] + } + } + } + } +} +``` + +#### Adding and removing reactions {/* #adding-and-removing-reactions */} + +Clients send a `hotsock.messageReaction` message on the WebSocket with the target message ID, the reaction value, and an explicit `add` or `remove` action: + +``` +> {"event":"hotsock.messageReaction", "channel":"room.123", "data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW", "reaction":"thumbsup", "action":"add"}} +``` + +All channel subscribers immediately receive a `hotsock.messageReactionAdded` event: + +``` +< {"id":"01JB4K7M2N...","event":"hotsock.messageReactionAdded","channel":"room.123","data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW","reaction":"thumbsup","action":"add"},"meta":{"uid":"Jim","umd":null}} +``` + +Reaction data can be emoji characters, Slack-style colon-wrapped strings like `:thumbsup:`, or any short string up to 100 bytes. Each user can have one reaction per reaction value per message. + +#### Reactions in message history {/* #reactions-in-message-history */} + +[`listMessages`](/docs/connections/client-http-api/#connection/listMessages) responses now include a `reactions` object on each message that has reactions, keyed by reaction value: + +```json +{ + "messages": [ + { + "id": "01JA3S0P7FB7WVYS67X316M32S", + "event": "chat", + "channel": "room.123", + "data": "Wow. Can we make it a different moment?", + "meta": { "uid": "Jim", "umd": null }, + // highlight-next-line + "reactions": { "thumbsup": { "count": 2 }, "heart": { "count": 1 } } + } + ] +} +``` + +Pass [`expandReactions: true`](/docs/connections/client-http-api/#connection/listMessages.expandReactions) to also include an `items` array per reaction with the user behind each one: + +```json +"reactions": { + "thumbsup": { + "count": 2, + "items": [ + { "uid": "Jim", "umd": { "name": "Jim Halpert" } }, + { "uid": "Pam" } + ] + } +} +``` + +You can also fetch reactions for a single message with the dedicated [`connection/listReactions`](/docs/connections/client-http-api/#connection/listReactions) endpoint. + +#### Pub/sub events for reactions {/* #pubsub-events-for-reactions */} + +[`hotsock.messageReactionAdded`](/docs/server-api/events/#hotsock.messageReactionAdded) and [`hotsock.messageReactionRemoved`](/docs/server-api/events/#hotsock.messageReactionRemoved) events are emitted to SNS/EventBridge after reactions are persisted, gated by the [`emitPubSubEvent`](/docs/connections/claims/#channels.messages.emitPubSubEvent) directive on the matching message event pattern. TTL-driven reaction deletes (when a message expires) do not trigger `messageReactionRemoved`. + ### Presence member events on pub/sub (SNS/EventBridge) {/* #presence-member-pubsub */} Presence channel member lifecycle events ([`hotsock.memberAdded`](/docs/server-api/events/#hotsock.memberAdded), [`hotsock.memberRemoved`](/docs/server-api/events/#hotsock.memberRemoved), [`hotsock.memberUpdated`](/docs/server-api/events/#hotsock.memberUpdated)) are now also emitted to SNS/EventBridge, gated by the existing [`PublishEventsToSNSParameter` / `PublishEventsToEventBridgeParameter`](/docs/server-api/events/#eventbridge-sns-enable-disable) settings. The pub/sub events are deduplicated by [`uid`](/docs/connections/claims/#uid) to match WebSocket fan-out semantics, so joining from a second device when you're already present doesn't trigger another `memberAdded`. diff --git a/docs/connections/claims.mdx b/docs/connections/claims.mdx index 704d18f..eed0923 100644 --- a/docs/connections/claims.mdx +++ b/docs/connections/claims.mdx @@ -226,7 +226,7 @@ If you wanted to allow access to the entire message history of a channel without `Object` (optional) - Manages the permissions and directives for client-initiated messages that are published directly to the WebSocket on the channel(s) for the specified events. Each object key is the name of an event and can include asterisks (\*) anywhere in the string to denote wildcards. Keys can also use the `#regex:` prefix to specify a regular expression pattern for matching event names. Each object value is another object with the settings for that event or event pattern. -Each object inside the messages object accepts [`broadcast`](#channels.messages.broadcast), [`echo`](#channels.messages.echo), [`emitPubSubEvent`](#channels.messages.emitPubSubEvent), [`publish`](#channels.messages.publish), [`scheduleBefore`](#channels.messages.scheduleBefore), and [`store`](#channels.messages.store) attributes. +Each object inside the messages object accepts [`broadcast`](#channels.messages.broadcast), [`echo`](#channels.messages.echo), [`emitPubSubEvent`](#channels.messages.emitPubSubEvent), [`publish`](#channels.messages.publish), [`react`](#channels.messages.react), [`scheduleBefore`](#channels.messages.scheduleBefore), and [`store`](#channels.messages.store) attributes. #### Regex patterns {/* #channels.messages--regex */} @@ -405,6 +405,81 @@ In the following example, since `chat.*` includes `chat.admin`, this connection ::: +#### `react` {/* #channels.messages.react */} + +`Boolean` or `Array[String]` (optional) - Controls whether this connection can add or remove reactions on stored messages that match this event pattern. Reactions require an active channel subscription and a [`uid`](#uid) on the connection. Default is `false`. + +When set to `true`, any reaction value is permitted. When set to an array of strings, only reaction values in the list are allowed. An explicit `false` denies reactions. + +When a reaction is added or removed, all channel subscribers receive a `hotsock.messageReactionAdded` or `hotsock.messageReactionRemoved` message in real-time. Reaction counts are included in [`listMessages`](./client-http-api.mdx#connection/listMessages) responses, and individual reactions can be listed with [`listReactions`](./client-http-api.mdx#connection/listReactions). + +The following allows any reaction on `chat` messages in the "mychannel" channel: + +```json +{ + "channels": { + "mychannel": { + "subscribe": true, + "messages": { + "chat": { + "publish": true, + "echo": true, + "store": 31536000, + // highlight-next-line + "react": true + } + } + } + } +} +``` + +To restrict to specific reactions, pass an array: + +```json +{ + "channels": { + "mychannel": { + "subscribe": true, + "messages": { + "chat": { + "publish": true, + "store": 31536000, + // highlight-next-line + "react": ["thumbsup", "heart", "laugh"] + } + } + } + } +} +``` + +To add a reaction, send a `hotsock.messageReaction` message on the WebSocket: + +``` +> {"event":"hotsock.messageReaction", "channel":"mychannel", "data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW", "reaction":"thumbsup", "action":"add"}} +``` + +To remove a reaction: + +``` +> {"event":"hotsock.messageReaction", "channel":"mychannel", "data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW", "reaction":"thumbsup", "action":"remove"}} +``` + +All channel subscribers receive the reaction event: + +``` +< {"id":"01JB4K7M2N...","event":"hotsock.messageReactionAdded","channel":"mychannel","data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW","reaction":"thumbsup","action":"add"},"meta":{"uid":"Jim","umd":null}} +``` + +Reaction data must not be blank, must be no more than 100 bytes, and must not contain number signs (`#`). Reactions can be emoji characters, Slack-style colon-wrapped strings like `:thumbsup:`, or any other short string. + +:::info +Reactions can only be added to stored messages (messages published with a [`store`](#channels.messages.store) TTL). The target message must exist and not be expired. Each user can have one reaction per reaction value per message. +::: + +Also available via the Client HTTP API at [`connection/publishMessage`](./client-http-api.mdx#connection/publishMessage) using `"event": "hotsock.messageReaction"` with the reaction payload as `data`, or via the server-side [Lambda](../server-api/publish-messages.mdx#publish-with-lambda) and [HTTP URL](../server-api/publish-messages.mdx#publish-with-http-url) publish APIs. + #### `scheduleBefore` {/* #channels.messages.scheduleBefore */} `NumericDate` (optional) - If [client-initiated message publishing](#channels.messages.publish) is permitted for this channel and event, the `scheduleBefore` attribute specifies the furthest out future time that this connection can schedule messages for delivery. If supplied, the schedule before time must be expressed as a Unix timestamp - the number of seconds since the Unix epoch. By default, or if `scheduleBefore` is provided as `0` or any timestamp in the past, scheduled message publishing is not permitted. diff --git a/docs/connections/client-http-api.mdx b/docs/connections/client-http-api.mdx index e131b8f..ad7333e 100644 --- a/docs/connections/client-http-api.mdx +++ b/docs/connections/client-http-api.mdx @@ -5,7 +5,7 @@ toc_max_heading_level: 4 # Client HTTP API -In addition to WebSocket interactions, connected clients can also make use of an HTTP API for publishing messages, listing message history, reading and writing channel storage, and updating user metadata. All requests **_must_ be `POST` requests** and **_must_ have URL-encoded `connectionId` and `connectionSecret`** query parameters set. +In addition to WebSocket interactions, connected clients can also make use of an HTTP API for publishing messages, listing message history, listing reactions, reading and writing channel storage, and updating user metadata. All requests **_must_ be `POST` requests** and **_must_ have URL-encoded `connectionId` and `connectionSecret`** query parameters set. ## Determine your API URL {/* #determine-your-api-url */} @@ -59,6 +59,8 @@ When issuing a connect or subscribe token, specify the [`historyStart`](./claims This endpoint will return up to `limit` messages (default 100) or up to 1MB of data, whichever comes first. Fetch subsequent pages using `before` or `after`, depending on your use case. +If any returned messages have reactions, a `reactions` object is included on each message, keyed by reaction value with `{count, items?}` per entry. By default only `count` is set. Pass [`expandReactions: true`](#connection/listMessages.expandReactions) to also populate `items` with one entry per reacting user. + #### `after` {/* #connection/listMessages.after */} String (optional) - Load messages after (newer than) the message with the ID specified here. Has no effect if `reverse` is `true`. @@ -79,6 +81,10 @@ Example: `01JA3HVNF6E89HDT1ABRFWJ8C8` String (required) - The name of the channel to query message history. +#### `expandReactions` {/* #connection/listMessages.expandReactions */} + +Boolean (optional) - If `true`, the `reactions` object on each returned message is enriched with an `items` array per reaction containing the `uid` and `umd` of each reacting user. Default is `false`, which returns aggregated counts only. Messages with no reactions omit the field when `false` and emit `{}` when `true`. + #### `limit` {/* #connection/listMessages.limit */} Integer (optional) - The maximum number of messages to return. Default is `100`. @@ -126,7 +132,9 @@ fetch( "meta": { "uid": "Jim", "umd": null - } + }, + // highlight-next-line + "reactions": { "thumbsup": { "count": 2 }, "heart": { "count": 1 } } }, { "id": "01JA3S0GB6S3WNTV2S1RJ421TH", @@ -156,7 +164,93 @@ fetch( "meta": { "uid": "Pam", "umd": null + }, + // highlight-next-line + "reactions": { "heart": { "count": 3 } } + } + ] +} +``` + +With `expandReactions: true`, each entry also has an `items` array with one element per reacting user: + +```json +{ + "messages": [ + { + "id": "01JA3S0P7FB7WVYS67X316M32S", + "event": "my-event", + "channel": "my-channel", + "data": "Wow. Can we make it a different moment?", + "meta": { "uid": "Jim", "umd": null }, + // highlight-start + "reactions": { + "thumbsup": { + "count": 2, + "items": [ + { "uid": "Jim", "umd": { "name": "Jim Halpert" } }, + { "uid": "Pam" } + ] + }, + "heart": { + "count": 1, + "items": [{ "uid": "Dwight" }] + } } + // highlight-end + } + ] +} +``` + +## `connection/listReactions` {/* #connection/listReactions */} + +List the individual reactions on a specific stored message, including who reacted. The connection must be subscribed to the channel. + +#### `channel` {/* #connection/listReactions.channel */} + +`String` (required) - The name of the channel where the message was published. + +#### `messageId` {/* #connection/listReactions.messageId */} + +`String` (required) - The ID of the message to list reactions for. + +### Example {/* #connection/listReactions--example */} + +#### Request {/* #connection/listReactions--example-request */} + +```javascript +fetch( + "https://r6zcm2.lambda-url.us-east-1.on.aws/connection/listReactions?connectionId=fjlb_eHLIAMCKRg%3d&connectionSecret=SZy32Etv0KIbe4Jod6KH", + { + method: "POST", + body: JSON.stringify({ + channel: "my-channel", + messageId: "01JA3S0P7FB7WVYS67X316M32S", + }), + }, +) +``` + +#### Response {/* #connection/listReactions--example-response */} + +Expect a `200 OK` status code for successful reads. + +```json +{ + "reactions": [ + { + "reaction": "thumbsup", + "uid": "Jim", + "umd": { "name": "Jim Halpert" } + }, + { + "reaction": "thumbsup", + "uid": "Pam" + }, + { + "reaction": "heart", + "uid": "Dwight" } ] } diff --git a/docs/installation/changelog.mdx b/docs/installation/changelog.mdx index efbbb2b..d600168 100644 --- a/docs/installation/changelog.mdx +++ b/docs/installation/changelog.mdx @@ -2,6 +2,10 @@ ## v1.13.2 - May 18, 2026 {/* #v1.13.2 */} +- Add [message reactions](../connections/claims.mdx#channels.messages.react). Clients send `hotsock.messageReaction` events with `add` or `remove` actions, gated by a new [`react`](../connections/claims.mdx#channels.messages.react) directive on channel message event patterns. The `react` directive accepts `true` (allow all reactions), `false` (deny), or an array of strings (allow only specific reactions). Reactions require an active channel subscription, a [`uid`](../connections/claims.mdx#uid), and a stored target message that hasn't expired. Also available via the [Client HTTP API](../connections/client-http-api.mdx) and the server-side [Lambda](../server-api/publish-messages.mdx#publish-with-lambda) and [HTTP URL](../server-api/publish-messages.mdx#publish-with-http-url) publish APIs. +- Reaction counts are automatically included in [`listMessages`](../connections/client-http-api.mdx#connection/listMessages) responses on each message that has reactions. Pass [`expandReactions: true`](../connections/client-http-api.mdx#connection/listMessages.expandReactions) to also receive a list of reacting users per reaction value. +- Add [`connection/listReactions`](../connections/client-http-api.mdx#connection/listReactions) Client HTTP API endpoint to list individual reactions on a single message with user attribution. +- Add [`hotsock.messageReactionAdded`](../server-api/events.mdx#hotsock.messageReactionAdded) and [`hotsock.messageReactionRemoved`](../server-api/events.mdx#hotsock.messageReactionRemoved) pub/sub events to SNS/EventBridge, emitted from the DynamoDB stream processor after reactions are persisted. Gated by the [`emitPubSubEvent`](../connections/claims.mdx#channels.messages.emitPubSubEvent) directive. TTL-driven reaction deletes do not emit `messageReactionRemoved`. - Publish [`hotsock.memberAdded`](../server-api/events.mdx#hotsock.memberAdded), [`hotsock.memberRemoved`](../server-api/events.mdx#hotsock.memberRemoved), and [`hotsock.memberUpdated`](../server-api/events.mdx#hotsock.memberUpdated) presence channel events to SNS/EventBridge, deduplicated by `uid` to match WebSocket fan-out semantics. - Add [`heartbeatTimeout`](../connections/claims.mdx#heartbeatTimeout) connect-token claim for server-enforced connection heartbeats. When set (5–600 seconds), the client must send a [`hotsock.heartbeat`](../connections/keep-alive.mdx#send-hotsockheartbeat) message at least that often or the server will forcefully disconnect. Useful for [presence channels](../channels/presence.mdx) and other applications that need accurate dropped-connection detection beyond what API Gateway's idle timeout provides. - Add configurable [`limit`](../connections/client-http-api.mdx#connection/listMessages.limit) parameter to [`connection/listMessages`](../connections/client-http-api.mdx#connection/listMessages) (default 100). diff --git a/docs/server-api/events.mdx b/docs/server-api/events.mdx index 2cb4521..9169776 100644 --- a/docs/server-api/events.mdx +++ b/docs/server-api/events.mdx @@ -507,6 +507,97 @@ SNS filter attributes are limited to `channel`, `key`, and `trigger`. The data object type for this event is `channelStorage`. +## `hotsock.messageReactionAdded` {/* #hotsock.messageReactionAdded */} + +This event is sent whenever a reaction is added to a stored message. Reaction pub/sub events are emitted from the DynamoDB stream processor after the reaction is persisted. They are gated by the [`emitPubSubEvent`](../connections/claims.mdx#channels.messages.emitPubSubEvent) directive on the matching message event pattern (always enabled for WebSocket-initiated reactions). + +### `type` {/* #hotsock.messageReactionAdded--type */} + +This is always `hotsock.messageReactionAdded`. + +### `metadata` {/* #hotsock.messageReactionAdded--metadata */} + +- `channel` (String): The name of the channel where the reaction was added. +- `connectionId` (String): The identifier for the connection that added the reaction. Only present for client-initiated reactions. +- `hotsockVersion` (String): The version of the Hotsock installation that generated this event. +- `reaction` (String): The reaction value that was added. +- `sourceIp` (String): The IP address of the client. Only present if known. +- `trigger` (String): The kind of initiator, set to one of `client.websocket`, `client.http`, `server.lambda`, or `server.http`. +- `userAgent` (String): The User-Agent of the client. Only present if known. + +```json +{ + "channel": "my-channel", + "connectionId": "IDnAdd9kIAMCEsQ=", + "hotsockVersion": "1.13.2", + "reaction": "thumbsup", + "sourceIp": "12.34.56.78", + "trigger": "client.websocket", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" +} +``` + +### `data` {/* #hotsock.messageReactionAdded--data */} + +- `channel` (String): The name of the channel where the reaction was added. +- `messageId` (String): The ID of the message that was reacted to. +- `id` (String): The unique ID of this reaction ([ULID](https://github.com/ulid/spec)). +- `reaction` (String): The reaction value. +- `uid` (String): The user ID of the user who added the reaction. +- `umd` (JSON | null): The user metadata of the user who added the reaction. +- `createdAt` (String): The ISO3339 timestamp when the reaction was created. +- `expiresAt` (String | null): The RFC3339 timestamp when the reaction expires (inherits the parent message's TTL), or `null` if the message has no TTL. + +```json +{ + "channel": "my-channel", + "messageId": "01JA3S0TNEC2QBYMZRBMCEXGNW", + "id": "01JB4K7M2NC8QBYMZRBMCEXGNW", + "reaction": "thumbsup", + "uid": "Jim", + "umd": null, + "createdAt": "2026-05-18T19:30:00.000Z", + "expiresAt": "2027-05-18T19:30:00Z" +} +``` + +### `dataType` {/* #hotsock.messageReactionAdded--dataType */} + +The data object type for this event is `messageReaction`. + +## `hotsock.messageReactionRemoved` {/* #hotsock.messageReactionRemoved */} + +This event is sent whenever a reaction is explicitly removed from a stored message. TTL-driven reaction deletes (when a message expires) do not trigger this event. + +### `type` {/* #hotsock.messageReactionRemoved--type */} + +This is always `hotsock.messageReactionRemoved`. + +### `metadata` {/* #hotsock.messageReactionRemoved--metadata */} + +Same as [`hotsock.messageReactionAdded` metadata](#hotsock.messageReactionAdded--metadata). + +### `data` {/* #hotsock.messageReactionRemoved--data */} + +Same shape as [`hotsock.messageReactionAdded` data](#hotsock.messageReactionAdded--data). + +```json +{ + "channel": "my-channel", + "messageId": "01JA3S0TNEC2QBYMZRBMCEXGNW", + "id": "01JB4K7M2NC8QBYMZRBMCEXGNW", + "reaction": "thumbsup", + "uid": "Jim", + "umd": null, + "createdAt": "2026-05-18T19:30:00.000Z", + "expiresAt": "2027-05-18T19:30:00Z" +} +``` + +### `dataType` {/* #hotsock.messageReactionRemoved--dataType */} + +The data object type for this event is `messageReaction`. + ## `hotsock.memberAdded` {/* #hotsock.memberAdded */} This event is sent whenever a unique member is added to a [presence channel](../channels/presence.mdx). Member events are deduplicated by `uid` to match WebSocket fan-out semantics — if a user is already present on the channel from another connection, no `hotsock.memberAdded` event fires when an additional connection joins. diff --git a/src/components/HomepageBanner.js b/src/components/HomepageBanner.js index ccf1f36..a0de3c2 100644 --- a/src/components/HomepageBanner.js +++ b/src/components/HomepageBanner.js @@ -8,17 +8,17 @@ export default function HomepageBanner() {

- New in v1.13: presence pub/sub and heartbeat enforcement! + New in v1.13: message reactions and presence pub/sub! - New in v1.13: presence events on pub/sub and server-enforced - heartbeats! + New in v1.13: message reactions, presence events on pub/sub, + and server-enforced heartbeats!

Learn more diff --git a/src/components/landing/Capabilities.js b/src/components/landing/Capabilities.js index e1cef57..2533641 100644 --- a/src/components/landing/Capabilities.js +++ b/src/components/landing/Capabilities.js @@ -31,6 +31,14 @@ const CAPABILITIES = [ href: "/docs/server-api/events/", icon: "☷", }, + { + title: "Message reactions", + description: + "Add and remove reactions on stored messages with real-time fan-out and counts in history.", + href: "/docs/connections/claims/#channels.messages.react", + icon: "♥", + badge: "New", + }, { title: "Server-enforced heartbeats", description: diff --git a/src/components/landing/Hero.js b/src/components/landing/Hero.js index f184d9f..a5eeecc 100644 --- a/src/components/landing/Hero.js +++ b/src/components/landing/Hero.js @@ -18,11 +18,11 @@ function Hero() {
- Now available · v1.13 presence pub/sub & heartbeat enforcement + Now available · v1.13 message reactions & presence pub/sub

diff --git a/src/components/landing/ReleaseCadence.js b/src/components/landing/ReleaseCadence.js index 79c3404..2927d55 100644 --- a/src/components/landing/ReleaseCadence.js +++ b/src/components/landing/ReleaseCadence.js @@ -7,10 +7,10 @@ const RELEASES = [ { version: "v1.13", date: "May 2026", - title: "Presence pub/sub & heartbeat enforcement", + title: "Message reactions & presence pub/sub", summary: - "Presence member events on SNS/EventBridge and server-enforced heartbeats for accurate dropped-connection detection.", - href: "/blog/presence-pubsub-and-heartbeats", + "Add and remove reactions on stored messages, presence events on SNS/EventBridge, and server-enforced heartbeats.", + href: "/blog/message-reactions-and-presence-pubsub", }, { version: "v1.12", From 0075e2ef064a56c3dfaccca68e7cfc60cce94e73 Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 19 May 2026 20:56:23 -0700 Subject: [PATCH 16/34] skip v1.13.2, point to v1.13.3 as the release --- blog/2026-05-18-new-in-v1.13.mdx | 2 +- docs/installation/changelog.mdx | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/blog/2026-05-18-new-in-v1.13.mdx b/blog/2026-05-18-new-in-v1.13.mdx index 49f109f..9bba4f3 100644 --- a/blog/2026-05-18-new-in-v1.13.mdx +++ b/blog/2026-05-18-new-in-v1.13.mdx @@ -133,4 +133,4 @@ Valid values are 5 to 600 seconds. This is most useful on [presence channels](/d ### Wrapping up {/* #wrapping-up */} -Existing installations with auto-update enabled are already running v1.13.2 and have access to these features today. Other installations can be [manually updated](/docs/installation/updates/#manually-update-installation) at any time. A [full changelog](/docs/installation/changelog/#v1.13.2) is available with the complete list of changes included in this release. +Existing installations with auto-update enabled are already running v1.13.3 and have access to these features today. Other installations can be [manually updated](/docs/installation/updates/#manually-update-installation) at any time. A [full changelog](/docs/installation/changelog/#v1.13.3) is available with the complete list of changes included in this release. diff --git a/docs/installation/changelog.mdx b/docs/installation/changelog.mdx index d600168..84e2a97 100644 --- a/docs/installation/changelog.mdx +++ b/docs/installation/changelog.mdx @@ -1,6 +1,6 @@ # Changelog -## v1.13.2 - May 18, 2026 {/* #v1.13.2 */} +## v1.13.3 - May 19, 2026 {/* #v1.13.3 */} - Add [message reactions](../connections/claims.mdx#channels.messages.react). Clients send `hotsock.messageReaction` events with `add` or `remove` actions, gated by a new [`react`](../connections/claims.mdx#channels.messages.react) directive on channel message event patterns. The `react` directive accepts `true` (allow all reactions), `false` (deny), or an array of strings (allow only specific reactions). Reactions require an active channel subscription, a [`uid`](../connections/claims.mdx#uid), and a stored target message that hasn't expired. Also available via the [Client HTTP API](../connections/client-http-api.mdx) and the server-side [Lambda](../server-api/publish-messages.mdx#publish-with-lambda) and [HTTP URL](../server-api/publish-messages.mdx#publish-with-http-url) publish APIs. - Reaction counts are automatically included in [`listMessages`](../connections/client-http-api.mdx#connection/listMessages) responses on each message that has reactions. Pass [`expandReactions: true`](../connections/client-http-api.mdx#connection/listMessages.expandReactions) to also receive a list of reacting users per reaction value. @@ -14,6 +14,10 @@ - Build with Go 1.26.3. - Update all aws-sdk-go-v2 SDK modules to their latest versions (as of [2026-05-12](https://github.com/aws/aws-sdk-go-v2/releases/tag/release-2026-05-12)). +## v1.13.2 - May 18, 2026 {/* #v1.13.2 */} + +- Skip this release. See [v1.13.3](#v1.13.3) for the full list of changes. + ## v1.12.0 - April 8, 2026 {/* #v1.12.0 */} - Add [channel storage](../connections/claims.mdx#channels.storage), a per-key persistent key-value store on channels. Storage permissions are configured per-key with wildcard and regex pattern support. Entries have independent TTLs, are delivered to [`observe`](../connections/claims.mdx#channels.storage.observe) subscribers on join, and skip fan-out when the value hasn't changed. Clients interact with storage via [`hotsock.channelStorageSet`](../connections/claims.mdx#channels.storage.set) / [`hotsock.channelStorageGet`](../connections/claims.mdx#channels.storage.get) on the WebSocket or the [`connection/channelStorageGet`](../connections/client-http-api.mdx#connection/channelStorageGet), [`connection/channelStorageSet`](../connections/client-http-api.mdx#connection/channelStorageSet), and [`connection/channelStorageList`](../connections/client-http-api.mdx#connection/channelStorageList) Client HTTP API endpoints. Storage operations do not require an active channel subscription. Server-side writes are available via the Lambda and HTTP publish APIs using `"event": "hotsock.channelStorageSet"` with a `key` field. From 2311b9a20481cfa3d72fd12a59e02926640b095c Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 19 May 2026 20:59:29 -0700 Subject: [PATCH 17/34] changelog: collapse to v1.13.0, remove version skips --- blog/2026-05-18-new-in-v1.13.mdx | 2 +- docs/installation/changelog.mdx | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/blog/2026-05-18-new-in-v1.13.mdx b/blog/2026-05-18-new-in-v1.13.mdx index 9bba4f3..4b0c62f 100644 --- a/blog/2026-05-18-new-in-v1.13.mdx +++ b/blog/2026-05-18-new-in-v1.13.mdx @@ -133,4 +133,4 @@ Valid values are 5 to 600 seconds. This is most useful on [presence channels](/d ### Wrapping up {/* #wrapping-up */} -Existing installations with auto-update enabled are already running v1.13.3 and have access to these features today. Other installations can be [manually updated](/docs/installation/updates/#manually-update-installation) at any time. A [full changelog](/docs/installation/changelog/#v1.13.3) is available with the complete list of changes included in this release. +Existing installations with auto-update enabled are already running v1.13 and have access to these features today. Other installations can be [manually updated](/docs/installation/updates/#manually-update-installation) at any time. A [full changelog](/docs/installation/changelog/#v1.13.0) is available with the complete list of changes included in this release. diff --git a/docs/installation/changelog.mdx b/docs/installation/changelog.mdx index 84e2a97..34ff890 100644 --- a/docs/installation/changelog.mdx +++ b/docs/installation/changelog.mdx @@ -1,6 +1,6 @@ # Changelog -## v1.13.3 - May 19, 2026 {/* #v1.13.3 */} +## v1.13.0 - May 19, 2026 {/* #v1.13.0 */} - Add [message reactions](../connections/claims.mdx#channels.messages.react). Clients send `hotsock.messageReaction` events with `add` or `remove` actions, gated by a new [`react`](../connections/claims.mdx#channels.messages.react) directive on channel message event patterns. The `react` directive accepts `true` (allow all reactions), `false` (deny), or an array of strings (allow only specific reactions). Reactions require an active channel subscription, a [`uid`](../connections/claims.mdx#uid), and a stored target message that hasn't expired. Also available via the [Client HTTP API](../connections/client-http-api.mdx) and the server-side [Lambda](../server-api/publish-messages.mdx#publish-with-lambda) and [HTTP URL](../server-api/publish-messages.mdx#publish-with-http-url) publish APIs. - Reaction counts are automatically included in [`listMessages`](../connections/client-http-api.mdx#connection/listMessages) responses on each message that has reactions. Pass [`expandReactions: true`](../connections/client-http-api.mdx#connection/listMessages.expandReactions) to also receive a list of reacting users per reaction value. @@ -14,10 +14,6 @@ - Build with Go 1.26.3. - Update all aws-sdk-go-v2 SDK modules to their latest versions (as of [2026-05-12](https://github.com/aws/aws-sdk-go-v2/releases/tag/release-2026-05-12)). -## v1.13.2 - May 18, 2026 {/* #v1.13.2 */} - -- Skip this release. See [v1.13.3](#v1.13.3) for the full list of changes. - ## v1.12.0 - April 8, 2026 {/* #v1.12.0 */} - Add [channel storage](../connections/claims.mdx#channels.storage), a per-key persistent key-value store on channels. Storage permissions are configured per-key with wildcard and regex pattern support. Entries have independent TTLs, are delivered to [`observe`](../connections/claims.mdx#channels.storage.observe) subscribers on join, and skip fan-out when the value hasn't changed. Clients interact with storage via [`hotsock.channelStorageSet`](../connections/claims.mdx#channels.storage.set) / [`hotsock.channelStorageGet`](../connections/claims.mdx#channels.storage.get) on the WebSocket or the [`connection/channelStorageGet`](../connections/client-http-api.mdx#connection/channelStorageGet), [`connection/channelStorageSet`](../connections/client-http-api.mdx#connection/channelStorageSet), and [`connection/channelStorageList`](../connections/client-http-api.mdx#connection/channelStorageList) Client HTTP API endpoints. Storage operations do not require an active channel subscription. Server-side writes are available via the Lambda and HTTP publish APIs using `"event": "hotsock.channelStorageSet"` with a `key` field. From 2f02ff1a0d5b0c98ba4a5678a821b73d1d2c6181 Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 19 May 2026 21:00:03 -0700 Subject: [PATCH 18/34] blog: update post date to May 19 --- blog/{2026-05-18-new-in-v1.13.mdx => 2026-05-19-new-in-v1.13.mdx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename blog/{2026-05-18-new-in-v1.13.mdx => 2026-05-19-new-in-v1.13.mdx} (100%) diff --git a/blog/2026-05-18-new-in-v1.13.mdx b/blog/2026-05-19-new-in-v1.13.mdx similarity index 100% rename from blog/2026-05-18-new-in-v1.13.mdx rename to blog/2026-05-19-new-in-v1.13.mdx From 30a6d8e85ebca7582bc0f60e9a2d20ef4c4a2337 Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 19 May 2026 21:16:02 -0700 Subject: [PATCH 19/34] changelog: drop DynamoDB stream processor implementation detail --- docs/installation/changelog.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/changelog.mdx b/docs/installation/changelog.mdx index 34ff890..cf0d2b6 100644 --- a/docs/installation/changelog.mdx +++ b/docs/installation/changelog.mdx @@ -5,7 +5,7 @@ - Add [message reactions](../connections/claims.mdx#channels.messages.react). Clients send `hotsock.messageReaction` events with `add` or `remove` actions, gated by a new [`react`](../connections/claims.mdx#channels.messages.react) directive on channel message event patterns. The `react` directive accepts `true` (allow all reactions), `false` (deny), or an array of strings (allow only specific reactions). Reactions require an active channel subscription, a [`uid`](../connections/claims.mdx#uid), and a stored target message that hasn't expired. Also available via the [Client HTTP API](../connections/client-http-api.mdx) and the server-side [Lambda](../server-api/publish-messages.mdx#publish-with-lambda) and [HTTP URL](../server-api/publish-messages.mdx#publish-with-http-url) publish APIs. - Reaction counts are automatically included in [`listMessages`](../connections/client-http-api.mdx#connection/listMessages) responses on each message that has reactions. Pass [`expandReactions: true`](../connections/client-http-api.mdx#connection/listMessages.expandReactions) to also receive a list of reacting users per reaction value. - Add [`connection/listReactions`](../connections/client-http-api.mdx#connection/listReactions) Client HTTP API endpoint to list individual reactions on a single message with user attribution. -- Add [`hotsock.messageReactionAdded`](../server-api/events.mdx#hotsock.messageReactionAdded) and [`hotsock.messageReactionRemoved`](../server-api/events.mdx#hotsock.messageReactionRemoved) pub/sub events to SNS/EventBridge, emitted from the DynamoDB stream processor after reactions are persisted. Gated by the [`emitPubSubEvent`](../connections/claims.mdx#channels.messages.emitPubSubEvent) directive. TTL-driven reaction deletes do not emit `messageReactionRemoved`. +- Add [`hotsock.messageReactionAdded`](../server-api/events.mdx#hotsock.messageReactionAdded) and [`hotsock.messageReactionRemoved`](../server-api/events.mdx#hotsock.messageReactionRemoved) pub/sub events to SNS/EventBridge when reactions are added or removed. Gated by the [`emitPubSubEvent`](../connections/claims.mdx#channels.messages.emitPubSubEvent) directive. TTL-driven reaction deletes do not emit `messageReactionRemoved`. - Publish [`hotsock.memberAdded`](../server-api/events.mdx#hotsock.memberAdded), [`hotsock.memberRemoved`](../server-api/events.mdx#hotsock.memberRemoved), and [`hotsock.memberUpdated`](../server-api/events.mdx#hotsock.memberUpdated) presence channel events to SNS/EventBridge, deduplicated by `uid` to match WebSocket fan-out semantics. - Add [`heartbeatTimeout`](../connections/claims.mdx#heartbeatTimeout) connect-token claim for server-enforced connection heartbeats. When set (5–600 seconds), the client must send a [`hotsock.heartbeat`](../connections/keep-alive.mdx#send-hotsockheartbeat) message at least that often or the server will forcefully disconnect. Useful for [presence channels](../channels/presence.mdx) and other applications that need accurate dropped-connection detection beyond what API Gateway's idle timeout provides. - Add configurable [`limit`](../connections/client-http-api.mdx#connection/listMessages.limit) parameter to [`connection/listMessages`](../connections/client-http-api.mdx#connection/listMessages) (default 100). From f54c02454b2ed921869b8c5bcfd2ab4ded67f0c4 Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 19 May 2026 21:21:30 -0700 Subject: [PATCH 20/34] drop listMessages limit parameter from docs (internal testing aid) --- docs/connections/client-http-api.mdx | 6 +----- docs/installation/changelog.mdx | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/connections/client-http-api.mdx b/docs/connections/client-http-api.mdx index ad7333e..ff3df6a 100644 --- a/docs/connections/client-http-api.mdx +++ b/docs/connections/client-http-api.mdx @@ -57,7 +57,7 @@ List the message history for a channel. By default, a connection can only list m When issuing a connect or subscribe token, specify the [`historyStart`](./claims.mdx#channels.historyStart) claim to expand visibility to message history beyond the lifetime of the active subscription. -This endpoint will return up to `limit` messages (default 100) or up to 1MB of data, whichever comes first. Fetch subsequent pages using `before` or `after`, depending on your use case. +This endpoint will return up to 100 messages or up to 1MB of data, whichever comes first. Fetch subsequent pages using `before` or `after`, depending on your use case. If any returned messages have reactions, a `reactions` object is included on each message, keyed by reaction value with `{count, items?}` per entry. By default only `count` is set. Pass [`expandReactions: true`](#connection/listMessages.expandReactions) to also populate `items` with one entry per reacting user. @@ -85,10 +85,6 @@ String (required) - The name of the channel to query message history. Boolean (optional) - If `true`, the `reactions` object on each returned message is enriched with an `items` array per reaction containing the `uid` and `umd` of each reacting user. Default is `false`, which returns aggregated counts only. Messages with no reactions omit the field when `false` and emit `{}` when `true`. -#### `limit` {/* #connection/listMessages.limit */} - -Integer (optional) - The maximum number of messages to return. Default is `100`. - #### `reverse` {/* #connection/listMessages.reverse */} Boolean (optional) - If set to `true`, lists messages from newest to oldest. Default is `false`. diff --git a/docs/installation/changelog.mdx b/docs/installation/changelog.mdx index cf0d2b6..59efafb 100644 --- a/docs/installation/changelog.mdx +++ b/docs/installation/changelog.mdx @@ -8,7 +8,6 @@ - Add [`hotsock.messageReactionAdded`](../server-api/events.mdx#hotsock.messageReactionAdded) and [`hotsock.messageReactionRemoved`](../server-api/events.mdx#hotsock.messageReactionRemoved) pub/sub events to SNS/EventBridge when reactions are added or removed. Gated by the [`emitPubSubEvent`](../connections/claims.mdx#channels.messages.emitPubSubEvent) directive. TTL-driven reaction deletes do not emit `messageReactionRemoved`. - Publish [`hotsock.memberAdded`](../server-api/events.mdx#hotsock.memberAdded), [`hotsock.memberRemoved`](../server-api/events.mdx#hotsock.memberRemoved), and [`hotsock.memberUpdated`](../server-api/events.mdx#hotsock.memberUpdated) presence channel events to SNS/EventBridge, deduplicated by `uid` to match WebSocket fan-out semantics. - Add [`heartbeatTimeout`](../connections/claims.mdx#heartbeatTimeout) connect-token claim for server-enforced connection heartbeats. When set (5–600 seconds), the client must send a [`hotsock.heartbeat`](../connections/keep-alive.mdx#send-hotsockheartbeat) message at least that often or the server will forcefully disconnect. Useful for [presence channels](../channels/presence.mdx) and other applications that need accurate dropped-connection detection beyond what API Gateway's idle timeout provides. -- Add configurable [`limit`](../connections/client-http-api.mdx#connection/listMessages.limit) parameter to [`connection/listMessages`](../connections/client-http-api.mdx#connection/listMessages) (default 100). - Improve subscription cleanup throughput when many connections disconnect or unsubscribe at the same time. - Update the [Web Console](../server-api/web-console.mdx) to the latest build. - Build with Go 1.26.3. From 5d4d8182667a47401002480d0a1d6a92f8bb59b6 Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 19 May 2026 21:22:07 -0700 Subject: [PATCH 21/34] changelog: note web console reaction support --- docs/installation/changelog.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/changelog.mdx b/docs/installation/changelog.mdx index 59efafb..cb1261e 100644 --- a/docs/installation/changelog.mdx +++ b/docs/installation/changelog.mdx @@ -9,7 +9,7 @@ - Publish [`hotsock.memberAdded`](../server-api/events.mdx#hotsock.memberAdded), [`hotsock.memberRemoved`](../server-api/events.mdx#hotsock.memberRemoved), and [`hotsock.memberUpdated`](../server-api/events.mdx#hotsock.memberUpdated) presence channel events to SNS/EventBridge, deduplicated by `uid` to match WebSocket fan-out semantics. - Add [`heartbeatTimeout`](../connections/claims.mdx#heartbeatTimeout) connect-token claim for server-enforced connection heartbeats. When set (5–600 seconds), the client must send a [`hotsock.heartbeat`](../connections/keep-alive.mdx#send-hotsockheartbeat) message at least that often or the server will forcefully disconnect. Useful for [presence channels](../channels/presence.mdx) and other applications that need accurate dropped-connection detection beyond what API Gateway's idle timeout provides. - Improve subscription cleanup throughput when many connections disconnect or unsubscribe at the same time. -- Update the [Web Console](../server-api/web-console.mdx) to the latest build. +- Update the [Web Console](../server-api/web-console.mdx) with support for sending message reactions. - Build with Go 1.26.3. - Update all aws-sdk-go-v2 SDK modules to their latest versions (as of [2026-05-12](https://github.com/aws/aws-sdk-go-v2/releases/tag/release-2026-05-12)). From 9a17e3363e3dd7d68e642ff7837b9a48db78fe41 Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 19 May 2026 21:24:00 -0700 Subject: [PATCH 22/34] blog: use emoji for allowed reactions example --- blog/2026-05-19-new-in-v1.13.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/2026-05-19-new-in-v1.13.mdx b/blog/2026-05-19-new-in-v1.13.mdx index 4b0c62f..4cf848f 100644 --- a/blog/2026-05-19-new-in-v1.13.mdx +++ b/blog/2026-05-19-new-in-v1.13.mdx @@ -34,7 +34,7 @@ The `react` directive accepts `true` (allow any reaction), `false` (deny), or an "echo": true, "store": 31536000, // highlight-next-line - "react": ["thumbsup", "heart", "laugh"] + "react": ["👍", "❤️", "😂"] } } } From 5a80f6c6433a380692e0879c45bc3d83a1e62430 Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 19 May 2026 21:25:02 -0700 Subject: [PATCH 23/34] blog: use emoji in add/added reaction examples --- blog/2026-05-19-new-in-v1.13.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blog/2026-05-19-new-in-v1.13.mdx b/blog/2026-05-19-new-in-v1.13.mdx index 4cf848f..6fd55ea 100644 --- a/blog/2026-05-19-new-in-v1.13.mdx +++ b/blog/2026-05-19-new-in-v1.13.mdx @@ -47,13 +47,13 @@ The `react` directive accepts `true` (allow any reaction), `false` (deny), or an Clients send a `hotsock.messageReaction` message on the WebSocket with the target message ID, the reaction value, and an explicit `add` or `remove` action: ``` -> {"event":"hotsock.messageReaction", "channel":"room.123", "data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW", "reaction":"thumbsup", "action":"add"}} +> {"event":"hotsock.messageReaction", "channel":"room.123", "data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW", "reaction":"👍", "action":"add"}} ``` All channel subscribers immediately receive a `hotsock.messageReactionAdded` event: ``` -< {"id":"01JB4K7M2N...","event":"hotsock.messageReactionAdded","channel":"room.123","data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW","reaction":"thumbsup","action":"add"},"meta":{"uid":"Jim","umd":null}} +< {"id":"01JB4K7M2N...","event":"hotsock.messageReactionAdded","channel":"room.123","data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW","reaction":"👍","action":"add"},"meta":{"uid":"Jim","umd":null}} ``` Reaction data can be emoji characters, Slack-style colon-wrapped strings like `:thumbsup:`, or any short string up to 100 bytes. Each user can have one reaction per reaction value per message. From eb263ec1ddb51fb767e1ac364e0b129298305ef9 Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 19 May 2026 21:26:20 -0700 Subject: [PATCH 24/34] blog: use emoji consistently in all reaction examples --- blog/2026-05-19-new-in-v1.13.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blog/2026-05-19-new-in-v1.13.mdx b/blog/2026-05-19-new-in-v1.13.mdx index 6fd55ea..fab9d7b 100644 --- a/blog/2026-05-19-new-in-v1.13.mdx +++ b/blog/2026-05-19-new-in-v1.13.mdx @@ -72,7 +72,7 @@ Reaction data can be emoji characters, Slack-style colon-wrapped strings like `: "data": "Wow. Can we make it a different moment?", "meta": { "uid": "Jim", "umd": null }, // highlight-next-line - "reactions": { "thumbsup": { "count": 2 }, "heart": { "count": 1 } } + "reactions": { "👍": { "count": 2 }, "❤️": { "count": 1 } } } ] } @@ -82,7 +82,7 @@ Pass [`expandReactions: true`](/docs/connections/client-http-api/#connection/lis ```json "reactions": { - "thumbsup": { + "👍": { "count": 2, "items": [ { "uid": "Jim", "umd": { "name": "Jim Halpert" } }, From ceaa6f64a2c5491280ca5524664b7bd6c2912ce8 Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 19 May 2026 21:28:51 -0700 Subject: [PATCH 25/34] blog: move TTL-delete detail to docs only --- blog/2026-05-19-new-in-v1.13.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/2026-05-19-new-in-v1.13.mdx b/blog/2026-05-19-new-in-v1.13.mdx index fab9d7b..4cfad72 100644 --- a/blog/2026-05-19-new-in-v1.13.mdx +++ b/blog/2026-05-19-new-in-v1.13.mdx @@ -96,7 +96,7 @@ You can also fetch reactions for a single message with the dedicated [`connectio #### Pub/sub events for reactions {/* #pubsub-events-for-reactions */} -[`hotsock.messageReactionAdded`](/docs/server-api/events/#hotsock.messageReactionAdded) and [`hotsock.messageReactionRemoved`](/docs/server-api/events/#hotsock.messageReactionRemoved) events are emitted to SNS/EventBridge after reactions are persisted, gated by the [`emitPubSubEvent`](/docs/connections/claims/#channels.messages.emitPubSubEvent) directive on the matching message event pattern. TTL-driven reaction deletes (when a message expires) do not trigger `messageReactionRemoved`. +[`hotsock.messageReactionAdded`](/docs/server-api/events/#hotsock.messageReactionAdded) and [`hotsock.messageReactionRemoved`](/docs/server-api/events/#hotsock.messageReactionRemoved) events are emitted to SNS/EventBridge after reactions are persisted, gated by the [`emitPubSubEvent`](/docs/connections/claims/#channels.messages.emitPubSubEvent) directive on the matching message event pattern. ### Presence member events on pub/sub (SNS/EventBridge) {/* #presence-member-pubsub */} From 0100b5b02be7df619bc17edfaddfb79e5a20f16b Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 19 May 2026 21:32:51 -0700 Subject: [PATCH 26/34] events: fix reaction pub/sub docs to match actual EventBridge output --- docs/server-api/events.mdx | 37 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/docs/server-api/events.mdx b/docs/server-api/events.mdx index 9169776..0f5b3d3 100644 --- a/docs/server-api/events.mdx +++ b/docs/server-api/events.mdx @@ -509,7 +509,7 @@ The data object type for this event is `channelStorage`. ## `hotsock.messageReactionAdded` {/* #hotsock.messageReactionAdded */} -This event is sent whenever a reaction is added to a stored message. Reaction pub/sub events are emitted from the DynamoDB stream processor after the reaction is persisted. They are gated by the [`emitPubSubEvent`](../connections/claims.mdx#channels.messages.emitPubSubEvent) directive on the matching message event pattern (always enabled for WebSocket-initiated reactions). +This event is sent whenever a reaction is added to a stored message. Gated by the [`emitPubSubEvent`](../connections/claims.mdx#channels.messages.emitPubSubEvent) directive on the matching message event pattern. ### `type` {/* #hotsock.messageReactionAdded--type */} @@ -521,6 +521,7 @@ This is always `hotsock.messageReactionAdded`. - `connectionId` (String): The identifier for the connection that added the reaction. Only present for client-initiated reactions. - `hotsockVersion` (String): The version of the Hotsock installation that generated this event. - `reaction` (String): The reaction value that was added. +- `requestId` (String): The request ID for the Lambda invocation that originally accepted the reaction. - `sourceIp` (String): The IP address of the client. Only present if known. - `trigger` (String): The kind of initiator, set to one of `client.websocket`, `client.http`, `server.lambda`, or `server.http`. - `userAgent` (String): The User-Agent of the client. Only present if known. @@ -529,11 +530,12 @@ This is always `hotsock.messageReactionAdded`. { "channel": "my-channel", "connectionId": "IDnAdd9kIAMCEsQ=", - "hotsockVersion": "1.13.2", - "reaction": "thumbsup", + "hotsockVersion": "1.13.0", + "reaction": ":thumbsup:", + "requestId": "d8c81dec-cf77-40e5-b97e-5e4300c14fbf", "sourceIp": "12.34.56.78", "trigger": "client.websocket", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:150.0) Gecko/20100101 Firefox/150.0" } ``` @@ -551,13 +553,13 @@ This is always `hotsock.messageReactionAdded`. ```json { "channel": "my-channel", - "messageId": "01JA3S0TNEC2QBYMZRBMCEXGNW", - "id": "01JB4K7M2NC8QBYMZRBMCEXGNW", - "reaction": "thumbsup", - "uid": "Jim", - "umd": null, - "createdAt": "2026-05-18T19:30:00.000Z", - "expiresAt": "2027-05-18T19:30:00Z" + "messageId": "01KS12S57B17NWNE71GKX1VMY3", + "id": "01KS12SJW3VEKXTCQNW5T2PE5D", + "reaction": ":thumbsup:", + "uid": "james", + "umd": "miller", + "createdAt": "2026-05-19T21:38:12.483Z", + "expiresAt": "2026-05-19T21:54:39Z" } ``` @@ -581,19 +583,6 @@ Same as [`hotsock.messageReactionAdded` metadata](#hotsock.messageReactionAdded- Same shape as [`hotsock.messageReactionAdded` data](#hotsock.messageReactionAdded--data). -```json -{ - "channel": "my-channel", - "messageId": "01JA3S0TNEC2QBYMZRBMCEXGNW", - "id": "01JB4K7M2NC8QBYMZRBMCEXGNW", - "reaction": "thumbsup", - "uid": "Jim", - "umd": null, - "createdAt": "2026-05-18T19:30:00.000Z", - "expiresAt": "2027-05-18T19:30:00Z" -} -``` - ### `dataType` {/* #hotsock.messageReactionRemoved--dataType */} The data object type for this event is `messageReaction`. From f322284f0e7c5a157819e6102b16808a3853521e Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 19 May 2026 21:33:48 -0700 Subject: [PATCH 27/34] events: note messageId is a ULID --- docs/server-api/events.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/server-api/events.mdx b/docs/server-api/events.mdx index 0f5b3d3..48ba462 100644 --- a/docs/server-api/events.mdx +++ b/docs/server-api/events.mdx @@ -542,7 +542,7 @@ This is always `hotsock.messageReactionAdded`. ### `data` {/* #hotsock.messageReactionAdded--data */} - `channel` (String): The name of the channel where the reaction was added. -- `messageId` (String): The ID of the message that was reacted to. +- `messageId` (String): The ID of the message that was reacted to ([ULID](https://github.com/ulid/spec)). - `id` (String): The unique ID of this reaction ([ULID](https://github.com/ulid/spec)). - `reaction` (String): The reaction value. - `uid` (String): The user ID of the user who added the reaction. From 78c6d3bbf26ad48ac80751af562e9d410265dd60 Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 19 May 2026 21:35:13 -0700 Subject: [PATCH 28/34] events: use consistent sample data for reaction events --- docs/server-api/events.mdx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/server-api/events.mdx b/docs/server-api/events.mdx index 48ba462..5e9030f 100644 --- a/docs/server-api/events.mdx +++ b/docs/server-api/events.mdx @@ -531,11 +531,11 @@ This is always `hotsock.messageReactionAdded`. "channel": "my-channel", "connectionId": "IDnAdd9kIAMCEsQ=", "hotsockVersion": "1.13.0", - "reaction": ":thumbsup:", - "requestId": "d8c81dec-cf77-40e5-b97e-5e4300c14fbf", + "reaction": "👍", + "requestId": "9d1d380a-0152-4962-bbbb-6e721b81db52", "sourceIp": "12.34.56.78", "trigger": "client.websocket", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:150.0) Gecko/20100101 Firefox/150.0" + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" } ``` @@ -553,13 +553,13 @@ This is always `hotsock.messageReactionAdded`. ```json { "channel": "my-channel", - "messageId": "01KS12S57B17NWNE71GKX1VMY3", - "id": "01KS12SJW3VEKXTCQNW5T2PE5D", - "reaction": ":thumbsup:", - "uid": "james", - "umd": "miller", - "createdAt": "2026-05-19T21:38:12.483Z", - "expiresAt": "2026-05-19T21:54:39Z" + "messageId": "01HZAD885RZ308CJM4YK825G65", + "id": "01HZADC44KZ308CJM4YK825G65", + "reaction": "👍", + "uid": null, + "umd": null, + "createdAt": "2026-05-19T15:47:45.231Z", + "expiresAt": "2027-05-19T15:47:45Z" } ``` From 25b2092b6f3b3ccc6ce50608968faa84a31ff61b Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 19 May 2026 22:20:00 -0700 Subject: [PATCH 29/34] events: set hotsockVersion to 1.13.0 in all examples --- docs/server-api/events.mdx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/server-api/events.mdx b/docs/server-api/events.mdx index 5e9030f..1c479cb 100644 --- a/docs/server-api/events.mdx +++ b/docs/server-api/events.mdx @@ -17,7 +17,7 @@ Published events use the same shape for both EventBridge and SNS, where the payl "source": "hotsock.v1", "type": "hotsock.connected", "metadata": { - "hotsockVersion": "1.9.1" + "hotsockVersion": "1.13.0" }, "data": { "id": "IDnAdd9kIAMCEsQ=", @@ -95,7 +95,7 @@ If enabled, all events are published to a Hotsock-specific event bus in your acc "source": "hotsock.v1", "type": "hotsock.connected", "metadata": { - "hotsockVersion": "1.9.1" + "hotsockVersion": "1.13.0" }, "data": { "id": "IDnAdd9kIAMCEsQ=", @@ -155,7 +155,7 @@ This is always `hotsock.connected`. ```json { - "hotsockVersion": "1.9.1" + "hotsockVersion": "1.13.0" } ``` @@ -201,7 +201,7 @@ This is always `hotsock.disconnected`. ```json { - "hotsockVersion": "1.9.1" + "hotsockVersion": "1.13.0" } ``` @@ -249,7 +249,7 @@ This is always `hotsock.channelUpdated`. ```json { "channel": "my-channel", - "hotsockVersion": "1.9.1" + "hotsockVersion": "1.13.0" } ``` @@ -292,7 +292,7 @@ This is always `hotsock.messagePublished`. "channel": "my-channel", "connectionId": "IDnAdd9kIAMCEsQ=", "event": "my-event", - "hotsockVersion": "1.9.1", + "hotsockVersion": "1.13.0", "requestId": "9d1d380a-0152-4962-bbbb-6e721b81db52", "sourceIp": "12.34.56.78", "trigger": "client.websocket", @@ -337,7 +337,7 @@ This is always `hotsock.subscribed`. ```json { "channel": "my-channel", - "hotsockVersion": "1.9.1" + "hotsockVersion": "1.13.0" } ``` @@ -377,7 +377,7 @@ This is always `hotsock.unsubscribed`. ```json { "channel": "my-channel", - "hotsockVersion": "1.9.1" + "hotsockVersion": "1.13.0" } ``` @@ -415,7 +415,7 @@ This is always `hotsock.connectionUpdated`. ```json { - "hotsockVersion": "1.12.0" + "hotsockVersion": "1.13.0" } ``` @@ -473,7 +473,7 @@ SNS filter attributes are limited to `channel`, `key`, and `trigger`. { "channel": "my-channel", "connectionId": "IDnAdd9kIAMCEsQ=", - "hotsockVersion": "1.12.0", + "hotsockVersion": "1.13.0", "key": "status", "sourceIp": "12.34.56.78", "trigger": "client.websocket", From 5c54a6f34c67554cbfc28fcf5aa8abf42b2d777a Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 19 May 2026 22:22:09 -0700 Subject: [PATCH 30/34] events: duplicate metadata/data shape for messageReactionRemoved --- docs/server-api/events.mdx | 44 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/docs/server-api/events.mdx b/docs/server-api/events.mdx index 1c479cb..532ecc7 100644 --- a/docs/server-api/events.mdx +++ b/docs/server-api/events.mdx @@ -577,11 +577,51 @@ This is always `hotsock.messageReactionRemoved`. ### `metadata` {/* #hotsock.messageReactionRemoved--metadata */} -Same as [`hotsock.messageReactionAdded` metadata](#hotsock.messageReactionAdded--metadata). +- `channel` (String): The name of the channel where the reaction was removed. +- `connectionId` (String): The identifier for the connection that removed the reaction. Only present for client-initiated reactions. +- `hotsockVersion` (String): The version of the Hotsock installation that generated this event. +- `reaction` (String): The reaction value that was removed. +- `requestId` (String): The request ID for the Lambda invocation that originally accepted the reaction. +- `sourceIp` (String): The IP address of the client. Only present if known. +- `trigger` (String): The kind of initiator, set to one of `client.websocket`, `client.http`, `server.lambda`, or `server.http`. +- `userAgent` (String): The User-Agent of the client. Only present if known. + +```json +{ + "channel": "my-channel", + "connectionId": "IDnAdd9kIAMCEsQ=", + "hotsockVersion": "1.13.0", + "reaction": "👍", + "requestId": "9d1d380a-0152-4962-bbbb-6e721b81db52", + "sourceIp": "12.34.56.78", + "trigger": "client.websocket", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" +} +``` ### `data` {/* #hotsock.messageReactionRemoved--data */} -Same shape as [`hotsock.messageReactionAdded` data](#hotsock.messageReactionAdded--data). +- `channel` (String): The name of the channel where the reaction was removed. +- `messageId` (String): The ID of the message that was reacted to ([ULID](https://github.com/ulid/spec)). +- `id` (String): The unique ID of this reaction ([ULID](https://github.com/ulid/spec)). +- `reaction` (String): The reaction value. +- `uid` (String): The user ID of the user who removed the reaction. +- `umd` (JSON | null): The user metadata of the user who removed the reaction. +- `createdAt` (String): The ISO3339 timestamp when the reaction was originally created. +- `expiresAt` (String | null): The RFC3339 timestamp when the reaction would have expired, or `null` if the message has no TTL. + +```json +{ + "channel": "my-channel", + "messageId": "01HZAD885RZ308CJM4YK825G65", + "id": "01HZADC44KZ308CJM4YK825G65", + "reaction": "👍", + "uid": null, + "umd": null, + "createdAt": "2026-05-19T15:47:45.231Z", + "expiresAt": "2027-05-19T15:47:45Z" +} +``` ### `dataType` {/* #hotsock.messageReactionRemoved--dataType */} From 01fbaec72d0a0688587f993648d45f1a511886cc Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 19 May 2026 22:23:53 -0700 Subject: [PATCH 31/34] events: note removed metadata reflects the original add, not the removal --- docs/server-api/events.mdx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/server-api/events.mdx b/docs/server-api/events.mdx index 532ecc7..32ba42d 100644 --- a/docs/server-api/events.mdx +++ b/docs/server-api/events.mdx @@ -577,14 +577,18 @@ This is always `hotsock.messageReactionRemoved`. ### `metadata` {/* #hotsock.messageReactionRemoved--metadata */} +:::note +`connectionId`, `requestId`, `sourceIp`, `trigger`, and `userAgent` reflect the original request that *added* the reaction, not the request that triggered the removal. +::: + - `channel` (String): The name of the channel where the reaction was removed. -- `connectionId` (String): The identifier for the connection that removed the reaction. Only present for client-initiated reactions. +- `connectionId` (String): The identifier for the connection that originally added the reaction. Only present for client-initiated reactions. - `hotsockVersion` (String): The version of the Hotsock installation that generated this event. - `reaction` (String): The reaction value that was removed. -- `requestId` (String): The request ID for the Lambda invocation that originally accepted the reaction. -- `sourceIp` (String): The IP address of the client. Only present if known. -- `trigger` (String): The kind of initiator, set to one of `client.websocket`, `client.http`, `server.lambda`, or `server.http`. -- `userAgent` (String): The User-Agent of the client. Only present if known. +- `requestId` (String): The request ID for the Lambda invocation that originally added the reaction. +- `sourceIp` (String): The IP address of the client that originally added the reaction. Only present if known. +- `trigger` (String): The kind of initiator that originally added the reaction, set to one of `client.websocket`, `client.http`, `server.lambda`, or `server.http`. +- `userAgent` (String): The User-Agent of the client that originally added the reaction. Only present if known. ```json { From 455feda0cc05553c382c389c44b71910c307599c Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 19 May 2026 22:28:09 -0700 Subject: [PATCH 32/34] events: use consistent uid/umd sample data across all pub/sub examples --- docs/server-api/events.mdx | 40 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/server-api/events.mdx b/docs/server-api/events.mdx index 32ba42d..fd6870c 100644 --- a/docs/server-api/events.mdx +++ b/docs/server-api/events.mdx @@ -25,8 +25,8 @@ Published events use the same shape for both EventBridge and SNS, where the payl "disconnectedAt": null, "keepAlive": true, "sourceIp": "12.34.56.78", - "uid": null, - "umd": null, + "uid": "Jim", + "umd": { "name": "Jim Halpert" }, "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" }, "dataType": "connection" @@ -103,8 +103,8 @@ If enabled, all events are published to a Hotsock-specific event bus in your acc "disconnectedAt": null, "keepAlive": true, "sourceIp": "12.34.56.78", - "uid": null, - "umd": null, + "uid": "Jim", + "umd": { "name": "Jim Halpert" }, "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" }, "dataType": "connection" @@ -125,7 +125,7 @@ The following is a sample SNS message for a newly established connection. Most o "Type": "Notification", "MessageId": "4360060f-c95a-58d5-8a7c-58dbecd688ed", "TopicArn": "arn:aws:sns:us-east-1:111111111111:Hotsock-PubSub-EOLFPNQLL8CW-Topic-AZXpBXfkywWc", - "Message": "{\"source\":\"hotsock.v1\",\"type\":\"hotsock.connected\",\"metadata\":{\"hotsockVersion\":\"1.9.1\"},\"data\":{\"id\":\"YppzrdWfoAMCJBQ=\",\"umd\":null,\"connectedAt\":\"2024-05-31T19:21:47.747616027Z\",\"disconnectedAt\":null,\"keepAlive\":true,\"uid\":null,\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0\",\"sourceIp\":\"12.34.56.78\"},\"dataType\":\"connection\"}", + "Message": "{\"source\":\"hotsock.v1\",\"type\":\"hotsock.connected\",\"metadata\":{\"hotsockVersion\":\"1.13.0\"},\"data\":{\"id\":\"YppzrdWfoAMCJBQ=\",\"umd\":{\"name\":\"Jim Halpert\"},\"connectedAt\":\"2024-05-31T19:21:47.747616027Z\",\"disconnectedAt\":null,\"keepAlive\":true,\"uid\":\"Jim\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0\",\"sourceIp\":\"12.34.56.78\"},\"dataType\":\"connection\"}", "Timestamp": "2024-05-31T19:21:48.428Z", "SignatureVersion": "1", "Signature": "PSnfnie3xEi4XDiJtcDBJGnQbZS5rvRut+ZAngBnQS7GqA/SC4fZuRFNf5d3pjoOuWM8idp5qyS+SppkBEt2a32cxEIeCMbV7MVeCR1A5FVuKKwZpqmmhyKIVfEvn30htjAgyy7V/OOvvzecoR0rHdG1KcyOze2XQILY6e8AAz8o/3mwpYx+KO7N1Ifs3+zavbjS/nuZbVyPVSxAWn2J9fcOGje/QhPi3wYvnrhsIotxPrLefIOWiRY3ayB5kvdkTTbRW1DADv/daCB9o7wpi9JNFQqklNkVOURNE2pjW/A5m9P1Ah14I437sS7xGHFeAfyBJhf4GwANS7r4Lyb/HQ==", @@ -177,8 +177,8 @@ This is always `hotsock.connected`. "disconnectedAt": null, "keepAlive": true, "sourceIp": "12.34.56.78", - "uid": null, - "umd": null, + "uid": "Jim", + "umd": { "name": "Jim Halpert" }, "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" } ``` @@ -223,8 +223,8 @@ This is always `hotsock.disconnected`. "disconnectedAt": "2024-05-31T17:47:45.000000000Z", "keepAlive": true, "sourceIp": "12.34.56.78", - "uid": null, - "umd": null, + "uid": "Jim", + "umd": { "name": "Jim Halpert" }, "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" } ``` @@ -356,8 +356,8 @@ The data object type for this event is `subscription`. { "channel": "my-channel", "connectionId": "IDnAdd9kIAMCEsQ=", - "uid": null, - "umd": null + "uid": "Jim", + "umd": { "name": "Jim Halpert" } } ``` @@ -392,8 +392,8 @@ This is always `hotsock.unsubscribed`. { "channel": "my-channel", "connectionId": "IDnAdd9kIAMCEsQ=", - "uid": null, - "umd": null + "uid": "Jim", + "umd": { "name": "Jim Halpert" } } ``` @@ -437,8 +437,8 @@ This is always `hotsock.connectionUpdated`. "disconnectedAt": null, "keepAlive": true, "sourceIp": "12.34.56.78", - "uid": "12345", - "umd": { "name": "Dwight", "status": "away" }, + "uid": "Jim", + "umd": { "name": "Jim Halpert" }, "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" } ``` @@ -499,7 +499,7 @@ SNS filter attributes are limited to `channel`, `key`, and `trigger`. "dataPrevious": "away", "expired": false, "expiresAt": "2026-04-03T12:00:00Z", - "meta": { "uid": "12345", "umd": null } + "meta": { "uid": "Jim", "umd": { "name": "Jim Halpert" } } } ``` @@ -556,8 +556,8 @@ This is always `hotsock.messageReactionAdded`. "messageId": "01HZAD885RZ308CJM4YK825G65", "id": "01HZADC44KZ308CJM4YK825G65", "reaction": "👍", - "uid": null, - "umd": null, + "uid": "Jim", + "umd": { "name": "Jim Halpert" }, "createdAt": "2026-05-19T15:47:45.231Z", "expiresAt": "2027-05-19T15:47:45Z" } @@ -620,8 +620,8 @@ This is always `hotsock.messageReactionRemoved`. "messageId": "01HZAD885RZ308CJM4YK825G65", "id": "01HZADC44KZ308CJM4YK825G65", "reaction": "👍", - "uid": null, - "umd": null, + "uid": "Jim", + "umd": { "name": "Jim Halpert" }, "createdAt": "2026-05-19T15:47:45.231Z", "expiresAt": "2027-05-19T15:47:45Z" } From 29cd5b7afeca48abe16fdd33ff163a227dce9724 Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 19 May 2026 22:29:22 -0700 Subject: [PATCH 33/34] events: use uid 12345 with first_name/last_name umd, keep member names for presence --- docs/server-api/events.mdx | 40 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/server-api/events.mdx b/docs/server-api/events.mdx index fd6870c..9467c42 100644 --- a/docs/server-api/events.mdx +++ b/docs/server-api/events.mdx @@ -25,8 +25,8 @@ Published events use the same shape for both EventBridge and SNS, where the payl "disconnectedAt": null, "keepAlive": true, "sourceIp": "12.34.56.78", - "uid": "Jim", - "umd": { "name": "Jim Halpert" }, + "uid": "12345", + "umd": { "first_name": "Jim", "last_name": "Halpert" }, "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" }, "dataType": "connection" @@ -103,8 +103,8 @@ If enabled, all events are published to a Hotsock-specific event bus in your acc "disconnectedAt": null, "keepAlive": true, "sourceIp": "12.34.56.78", - "uid": "Jim", - "umd": { "name": "Jim Halpert" }, + "uid": "12345", + "umd": { "first_name": "Jim", "last_name": "Halpert" }, "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" }, "dataType": "connection" @@ -125,7 +125,7 @@ The following is a sample SNS message for a newly established connection. Most o "Type": "Notification", "MessageId": "4360060f-c95a-58d5-8a7c-58dbecd688ed", "TopicArn": "arn:aws:sns:us-east-1:111111111111:Hotsock-PubSub-EOLFPNQLL8CW-Topic-AZXpBXfkywWc", - "Message": "{\"source\":\"hotsock.v1\",\"type\":\"hotsock.connected\",\"metadata\":{\"hotsockVersion\":\"1.13.0\"},\"data\":{\"id\":\"YppzrdWfoAMCJBQ=\",\"umd\":{\"name\":\"Jim Halpert\"},\"connectedAt\":\"2024-05-31T19:21:47.747616027Z\",\"disconnectedAt\":null,\"keepAlive\":true,\"uid\":\"Jim\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0\",\"sourceIp\":\"12.34.56.78\"},\"dataType\":\"connection\"}", + "Message": "{\"source\":\"hotsock.v1\",\"type\":\"hotsock.connected\",\"metadata\":{\"hotsockVersion\":\"1.13.0\"},\"data\":{\"id\":\"YppzrdWfoAMCJBQ=\",\"umd\":{\"first_name\":\"Jim\",\"last_name\":\"Halpert\"},\"connectedAt\":\"2024-05-31T19:21:47.747616027Z\",\"disconnectedAt\":null,\"keepAlive\":true,\"uid\":\"12345\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0\",\"sourceIp\":\"12.34.56.78\"},\"dataType\":\"connection\"}", "Timestamp": "2024-05-31T19:21:48.428Z", "SignatureVersion": "1", "Signature": "PSnfnie3xEi4XDiJtcDBJGnQbZS5rvRut+ZAngBnQS7GqA/SC4fZuRFNf5d3pjoOuWM8idp5qyS+SppkBEt2a32cxEIeCMbV7MVeCR1A5FVuKKwZpqmmhyKIVfEvn30htjAgyy7V/OOvvzecoR0rHdG1KcyOze2XQILY6e8AAz8o/3mwpYx+KO7N1Ifs3+zavbjS/nuZbVyPVSxAWn2J9fcOGje/QhPi3wYvnrhsIotxPrLefIOWiRY3ayB5kvdkTTbRW1DADv/daCB9o7wpi9JNFQqklNkVOURNE2pjW/A5m9P1Ah14I437sS7xGHFeAfyBJhf4GwANS7r4Lyb/HQ==", @@ -177,8 +177,8 @@ This is always `hotsock.connected`. "disconnectedAt": null, "keepAlive": true, "sourceIp": "12.34.56.78", - "uid": "Jim", - "umd": { "name": "Jim Halpert" }, + "uid": "12345", + "umd": { "first_name": "Jim", "last_name": "Halpert" }, "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" } ``` @@ -223,8 +223,8 @@ This is always `hotsock.disconnected`. "disconnectedAt": "2024-05-31T17:47:45.000000000Z", "keepAlive": true, "sourceIp": "12.34.56.78", - "uid": "Jim", - "umd": { "name": "Jim Halpert" }, + "uid": "12345", + "umd": { "first_name": "Jim", "last_name": "Halpert" }, "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" } ``` @@ -356,8 +356,8 @@ The data object type for this event is `subscription`. { "channel": "my-channel", "connectionId": "IDnAdd9kIAMCEsQ=", - "uid": "Jim", - "umd": { "name": "Jim Halpert" } + "uid": "12345", + "umd": { "first_name": "Jim", "last_name": "Halpert" } } ``` @@ -392,8 +392,8 @@ This is always `hotsock.unsubscribed`. { "channel": "my-channel", "connectionId": "IDnAdd9kIAMCEsQ=", - "uid": "Jim", - "umd": { "name": "Jim Halpert" } + "uid": "12345", + "umd": { "first_name": "Jim", "last_name": "Halpert" } } ``` @@ -437,8 +437,8 @@ This is always `hotsock.connectionUpdated`. "disconnectedAt": null, "keepAlive": true, "sourceIp": "12.34.56.78", - "uid": "Jim", - "umd": { "name": "Jim Halpert" }, + "uid": "12345", + "umd": { "first_name": "Jim", "last_name": "Halpert" }, "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" } ``` @@ -499,7 +499,7 @@ SNS filter attributes are limited to `channel`, `key`, and `trigger`. "dataPrevious": "away", "expired": false, "expiresAt": "2026-04-03T12:00:00Z", - "meta": { "uid": "Jim", "umd": { "name": "Jim Halpert" } } + "meta": { "uid": "12345", "umd": { "first_name": "Jim", "last_name": "Halpert" } } } ``` @@ -556,8 +556,8 @@ This is always `hotsock.messageReactionAdded`. "messageId": "01HZAD885RZ308CJM4YK825G65", "id": "01HZADC44KZ308CJM4YK825G65", "reaction": "👍", - "uid": "Jim", - "umd": { "name": "Jim Halpert" }, + "uid": "12345", + "umd": { "first_name": "Jim", "last_name": "Halpert" }, "createdAt": "2026-05-19T15:47:45.231Z", "expiresAt": "2027-05-19T15:47:45Z" } @@ -620,8 +620,8 @@ This is always `hotsock.messageReactionRemoved`. "messageId": "01HZAD885RZ308CJM4YK825G65", "id": "01HZADC44KZ308CJM4YK825G65", "reaction": "👍", - "uid": "Jim", - "umd": { "name": "Jim Halpert" }, + "uid": "12345", + "umd": { "first_name": "Jim", "last_name": "Halpert" }, "createdAt": "2026-05-19T15:47:45.231Z", "expiresAt": "2027-05-19T15:47:45Z" } From 7528f650736aa053f857b6794dc5416373018d25 Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 19 May 2026 22:29:37 -0700 Subject: [PATCH 34/34] events: use camelCase firstName/lastName in umd examples --- docs/server-api/events.mdx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/server-api/events.mdx b/docs/server-api/events.mdx index 9467c42..eefdc4e 100644 --- a/docs/server-api/events.mdx +++ b/docs/server-api/events.mdx @@ -26,7 +26,7 @@ Published events use the same shape for both EventBridge and SNS, where the payl "keepAlive": true, "sourceIp": "12.34.56.78", "uid": "12345", - "umd": { "first_name": "Jim", "last_name": "Halpert" }, + "umd": { "firstName": "Jim", "lastName": "Halpert" }, "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" }, "dataType": "connection" @@ -104,7 +104,7 @@ If enabled, all events are published to a Hotsock-specific event bus in your acc "keepAlive": true, "sourceIp": "12.34.56.78", "uid": "12345", - "umd": { "first_name": "Jim", "last_name": "Halpert" }, + "umd": { "firstName": "Jim", "lastName": "Halpert" }, "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" }, "dataType": "connection" @@ -125,7 +125,7 @@ The following is a sample SNS message for a newly established connection. Most o "Type": "Notification", "MessageId": "4360060f-c95a-58d5-8a7c-58dbecd688ed", "TopicArn": "arn:aws:sns:us-east-1:111111111111:Hotsock-PubSub-EOLFPNQLL8CW-Topic-AZXpBXfkywWc", - "Message": "{\"source\":\"hotsock.v1\",\"type\":\"hotsock.connected\",\"metadata\":{\"hotsockVersion\":\"1.13.0\"},\"data\":{\"id\":\"YppzrdWfoAMCJBQ=\",\"umd\":{\"first_name\":\"Jim\",\"last_name\":\"Halpert\"},\"connectedAt\":\"2024-05-31T19:21:47.747616027Z\",\"disconnectedAt\":null,\"keepAlive\":true,\"uid\":\"12345\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0\",\"sourceIp\":\"12.34.56.78\"},\"dataType\":\"connection\"}", + "Message": "{\"source\":\"hotsock.v1\",\"type\":\"hotsock.connected\",\"metadata\":{\"hotsockVersion\":\"1.13.0\"},\"data\":{\"id\":\"YppzrdWfoAMCJBQ=\",\"umd\":{\"firstName\":\"Jim\",\"lastName\":\"Halpert\"},\"connectedAt\":\"2024-05-31T19:21:47.747616027Z\",\"disconnectedAt\":null,\"keepAlive\":true,\"uid\":\"12345\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0\",\"sourceIp\":\"12.34.56.78\"},\"dataType\":\"connection\"}", "Timestamp": "2024-05-31T19:21:48.428Z", "SignatureVersion": "1", "Signature": "PSnfnie3xEi4XDiJtcDBJGnQbZS5rvRut+ZAngBnQS7GqA/SC4fZuRFNf5d3pjoOuWM8idp5qyS+SppkBEt2a32cxEIeCMbV7MVeCR1A5FVuKKwZpqmmhyKIVfEvn30htjAgyy7V/OOvvzecoR0rHdG1KcyOze2XQILY6e8AAz8o/3mwpYx+KO7N1Ifs3+zavbjS/nuZbVyPVSxAWn2J9fcOGje/QhPi3wYvnrhsIotxPrLefIOWiRY3ayB5kvdkTTbRW1DADv/daCB9o7wpi9JNFQqklNkVOURNE2pjW/A5m9P1Ah14I437sS7xGHFeAfyBJhf4GwANS7r4Lyb/HQ==", @@ -178,7 +178,7 @@ This is always `hotsock.connected`. "keepAlive": true, "sourceIp": "12.34.56.78", "uid": "12345", - "umd": { "first_name": "Jim", "last_name": "Halpert" }, + "umd": { "firstName": "Jim", "lastName": "Halpert" }, "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" } ``` @@ -224,7 +224,7 @@ This is always `hotsock.disconnected`. "keepAlive": true, "sourceIp": "12.34.56.78", "uid": "12345", - "umd": { "first_name": "Jim", "last_name": "Halpert" }, + "umd": { "firstName": "Jim", "lastName": "Halpert" }, "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" } ``` @@ -357,7 +357,7 @@ The data object type for this event is `subscription`. "channel": "my-channel", "connectionId": "IDnAdd9kIAMCEsQ=", "uid": "12345", - "umd": { "first_name": "Jim", "last_name": "Halpert" } + "umd": { "firstName": "Jim", "lastName": "Halpert" } } ``` @@ -393,7 +393,7 @@ This is always `hotsock.unsubscribed`. "channel": "my-channel", "connectionId": "IDnAdd9kIAMCEsQ=", "uid": "12345", - "umd": { "first_name": "Jim", "last_name": "Halpert" } + "umd": { "firstName": "Jim", "lastName": "Halpert" } } ``` @@ -438,7 +438,7 @@ This is always `hotsock.connectionUpdated`. "keepAlive": true, "sourceIp": "12.34.56.78", "uid": "12345", - "umd": { "first_name": "Jim", "last_name": "Halpert" }, + "umd": { "firstName": "Jim", "lastName": "Halpert" }, "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0" } ``` @@ -499,7 +499,7 @@ SNS filter attributes are limited to `channel`, `key`, and `trigger`. "dataPrevious": "away", "expired": false, "expiresAt": "2026-04-03T12:00:00Z", - "meta": { "uid": "12345", "umd": { "first_name": "Jim", "last_name": "Halpert" } } + "meta": { "uid": "12345", "umd": { "firstName": "Jim", "lastName": "Halpert" } } } ``` @@ -557,7 +557,7 @@ This is always `hotsock.messageReactionAdded`. "id": "01HZADC44KZ308CJM4YK825G65", "reaction": "👍", "uid": "12345", - "umd": { "first_name": "Jim", "last_name": "Halpert" }, + "umd": { "firstName": "Jim", "lastName": "Halpert" }, "createdAt": "2026-05-19T15:47:45.231Z", "expiresAt": "2027-05-19T15:47:45Z" } @@ -621,7 +621,7 @@ This is always `hotsock.messageReactionRemoved`. "id": "01HZADC44KZ308CJM4YK825G65", "reaction": "👍", "uid": "12345", - "umd": { "first_name": "Jim", "last_name": "Halpert" }, + "umd": { "firstName": "Jim", "lastName": "Halpert" }, "createdAt": "2026-05-19T15:47:45.231Z", "expiresAt": "2027-05-19T15:47:45Z" }