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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};
presets: [require.resolve("@docusaurus/core/lib/babel/preset")],
}
4 changes: 2 additions & 2 deletions blog/2024-10-14-new-in-v1.3.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ fetch(
{
method: "POST",
body: JSON.stringify({ channel: "my-channel" }),
}
},
)
```

Expand Down Expand Up @@ -133,7 +133,7 @@ fetch(
{
method: "POST",
body: JSON.stringify({ channel: "my-channel", event: "my-event" }),
}
},
)
```

Expand Down
160 changes: 160 additions & 0 deletions blog/2026-04-02-new-in-v1.12.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
---
slug: channel-storage-and-umd-updates
title: Channel storage and live user metadata updates
authors: [james]
tags: [releases, features, channels, presence]
description: Hotsock v1.12 adds per-key persistent channel storage with real-time sync, live user metadata updates without reconnecting, and new presence events for metadata changes.
---

Hotsock v1.12 introduces **channel storage** for persistent per-key state on channels and **live user metadata updates** so clients can change their user metadata ([`umd`](/docs/connections/claims/#umd)) without reconnecting. Both features are designed to reduce the amount of state your backend needs to manage and push more real-time coordination into Hotsock itself.

{/* truncate */}

### Channel storage

Channels can now have persistent key-value storage entries that subscribers can read, write, and observe in real-time. Storage entries have independent TTLs for flexible data retention and are automatically delivered to observers when they subscribe.

This is useful for any state that should be available to current and future subscribers of a channel — things like configuration, feature flags, room settings, shared cursors, or game state. Instead of fetching this from your backend after subscribing, the client gets it immediately as part of the subscribe flow.

#### Setting up permissions

Storage permissions are configured per-key using the new [`storage`](/docs/connections/claims/#channels.storage) directive inside channel claims. Keys support wildcards and regex patterns, just like channel names and event names.

```json
{
"exp": 1743580800,
"scope": "connect",
"channels": {
"game.lobby": {
"subscribe": true,
// highlight-start
"storage": {
"settings": {
"observe": true
},
"player.123": {
"observe": true,
"set": true,
"store": 86400,
// highlight-next-line
"emitPubSubEvent": true
}
}
// highlight-end
}
}
}
```

Each storage key directive supports these options:

- [**`observe`**](/docs/connections/claims/#channels.storage.observe) — receive `hotsock.channelStorageUpdated` messages in real-time when the value changes, and get current values automatically on subscribe
- [**`get`**](/docs/connections/claims/#channels.storage.get) — fetch the current value on demand with `hotsock.channelStorageGet`
- [**`set`**](/docs/connections/claims/#channels.storage.set) — write values with `hotsock.channelStorageSet`
- [**`store`**](/docs/connections/claims/#channels.storage.store) — TTL in seconds for entries written by this connection
- [**`emitPubSubEvent`**](/docs/connections/claims/#channels.storage.emitPubSubEvent) — trigger a [`hotsock.channelStorageUpdated`](/docs/server-api/events/#hotsock.channelStorageUpdated) pub/sub event to SNS/EventBridge when a value changes

#### Writing and reading storage

Clients interact with storage using `hotsock.channelStorageSet` and `hotsock.channelStorageGet` messages on the WebSocket, or via the [Client HTTP API](/docs/connections/client-http-api/#connection/channelStorageGet):

```
> {"event":"hotsock.channelStorageSet", "channel":"game.lobby", "key":"settings", "data":{"maxPlayers":4}}
```

```
> {"event":"hotsock.channelStorageGet", "channel":"game.lobby", "key":"settings"}
< {"event":"hotsock.channelStorageData","channel":"game.lobby","key":"settings","data":{"maxPlayers":4},"meta":{"uid":"host","umd":null}}
```

Writes that don't change the value are detected automatically and skip subscriber fan-out entirely, so you don't need to worry about deduplication on the client side. Setting `data` to `null` or `{}` clears the entry.

For client-initiated writes, storage entries track the [`uid`](/docs/connections/claims/#uid) and [`umd`](/docs/connections/claims/#umd) of the last writer in `meta`, so observers know who set a value.

#### Real-time sync on subscribe

When a connection subscribes to a channel, all storage entries matching their `observe` patterns are delivered as `hotsock.channelStorageUpdated` messages immediately after the `hotsock.subscribed` message. This means a new subscriber gets the current state without any additional requests — they're caught up the moment they join.

#### Scheduled storage writes

Storage writes support [scheduling for future delivery](/docs/channels/storage/#scheduled-writes) using `scheduleExpression`, just like scheduled messages. This is useful for things like expiring a game round, resetting a status after a timeout, or setting up state transitions at known times. Client-initiated scheduled writes require the [`scheduleBefore`](/docs/connections/claims/#channels.storage.scheduleBefore) permission on the matching storage key.

#### Server-side storage writes

Storage entries can also be set from the server side using the existing [Lambda](/docs/server-api/publish-messages/#publish-with-lambda) or [HTTP](/docs/server-api/publish-messages/#publish-with-http-url) publish APIs by specifying `"event": "hotsock.channelStorageSet"` with a [`key`](/docs/server-api/publish-messages/#message-format.key) field. Server-side writes bypass client permission checks, so they're always authorized. This is handy for initializing channel state from your backend before any clients connect.

For a full walkthrough of permissions, TTL behavior, and all the ways to interact with storage, see the [channel storage documentation](/docs/channels/storage/).

### Live user metadata updates

Previously, the only way to change a connection's [`umd`](/docs/connections/claims/#umd) was to disconnect and reconnect with a new token, or unsubscribe and resubscribe with a new subscribe token. Now clients can update their `umd` in place using `hotsock.umdUpdate`.

This works at two levels:

#### Per-channel updates

Update `umd` on a specific channel subscription by sending `hotsock.umdUpdate` with a channel and a [`umd`-scoped](/docs/connections/claims/#scope) token that has the [`umdUpdate`](/docs/connections/claims/#channels.umdUpdate) channel directive:

```json
{
"exp": 1743580800,
"scope": "umd",
"uid": "Dwight",
// highlight-next-line
"umd": { "status": "away" },
"channels": {
"presence.chat": {
// highlight-next-line
"umdUpdate": true
}
}
}
```

Then send the signed token on the WebSocket:

```
> {"event":"hotsock.umdUpdate", "channel":"presence.chat", "data":{"token":"eyJ..."}}
```

On [presence channels](/docs/channels/presence/), this triggers a new [`hotsock.memberUpdated`](/docs/channels/presence/#member-updated) event delivered to all members (including the initiator), so everyone sees the change immediately:

```
< {"event":"hotsock.memberUpdated","channel":"presence.chat","data":{"member":{"uid":"Dwight","umd":{"status":"away"}},"members":[{"uid":"Jim","umd":null},{"uid":"Dwight","umd":{"status":"away"}}]}}
```

This is great for status indicators, typing states, or any per-user metadata that changes during a session.

#### Connection-level updates

Update the connection's default `umd` by sending `hotsock.umdUpdate` without a channel. This requires the top-level [`umdUpdate`](/docs/connections/claims/#umdUpdate) claim. When [`umdPropagate`](/docs/connections/claims/#umdPropagate) is also enabled, the updated metadata is propagated to all existing subscriptions on the connection, triggering [`hotsock.memberUpdated`](/docs/channels/presence/#member-updated) on any presence channels.

```json
{
"exp": 1743580800,
"scope": "umd",
"uid": "Dwight",
// highlight-next-line
"umd": { "status": "away" },
// highlight-next-line
"umdUpdate": true,
// highlight-next-line
"umdPropagate": true
}
```

```
> {"event":"hotsock.umdUpdate", "data":{"token":"eyJ..."}}
```

Future subscriptions that are authorized by the connect token (implicit subscriptions) will automatically inherit the updated metadata from the connection, so you don't need to include the new `umd` in every subsequent subscribe token.

Both paths are also available via the Client HTTP API at [`connection/umdUpdate`](/docs/connections/client-http-api/#connection/umdUpdate).

#### New pub/sub event

A new [`hotsock.connectionUpdated`](/docs/server-api/events/#hotsock.connectionUpdated) pub/sub event is emitted to SNS/EventBridge when a connection's user metadata actually changes, so you can track user state changes on your backend.

### Wrapping up

Existing installations with auto-update enabled are already running v1.12 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.12.0) is available with the complete list of changes included in this release.
40 changes: 24 additions & 16 deletions docs/channels/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,28 @@ sidebar_label: Overview

# Channels Overview

Your applications can have one or more channels and each of your clients can choose which channels to subscribe to. If a client is subscribed to a particular channel, they will receive all messages sent on that channel.
Channels are how you organize and control access to real-time data in Hotsock. Clients subscribe to channels to receive messages, and your backend publishes messages to channels to reach those subscribers.

**Each channel subscription _must_ be authorized by a claim in the token**. This allows you to use channels to control access to different streams of information.
**Every channel subscription must be authorized by a claim in the JWT token.** This is how you control which clients can access which data — permissions are baked into the token at signing time, not configured globally.

For example, you may have a "leaderboard" channel that publishes messages that everyone in the application can see. At the same time, you may have a "game.123" channel that publishes all the events that occur in game ID 123, where only people playing or viewing that particular game can see those messages.
There is no limit to the number of channels your application can use, and channels do not need to be declared ahead of time. When a message is published to a channel, every currently subscribed client receives a copy.

There is no limit to the number of channels your application can have and channels do not need to be declared ahead of time. When any message is published to a channel, any currently subscribed listeners receive a copy of that message.
## Naming

Channel names can be any string between 1 to 256 characters and must not contain asterisks (`*`), hashes (`#`), commas (`,`), spaces, or newline characters. In JWT claims, channel keys support wildcards (`*`) and regex patterns (`#regex:`) for flexible permission matching.
Channel names can be any string between 1 and 256 characters. The following characters are **not allowed** in channel names:

## Events
- Asterisks (`*`)
- Number signs (`#`)
- Commas (`,`)
- Spaces and newline characters

In JWT claims, channel keys support wildcards (`*`) and [regex patterns](../connections/claims.mdx#channels--regex) (`#regex:`) for flexible permission matching across multiple channels.

**Each message published to a channel _must_ contain an event name**. Events allow for logically grouping types of messages sent to the same channel.
## Events

For example, if you have a "game.123" channel, perhaps you'd have "move" and "chat" events. In your handler code for these events, different events on the same channel allow you to maintain predictable message shape for different circumstances in the same context.
Every message published to a channel must include an **event name**. Events let you group different types of messages on the same channel so your client code can handle them separately.

In the end, messages received by a subscriber look something like this. The message `id` is Hotsock-generated.
For example, a `game.123` channel might have `move` and `chat` events:

```json
{
Expand All @@ -40,16 +45,19 @@ In the end, messages received by a subscriber look something like this. The mess
}
```

Your application will receive these messages on the same channel, but can handle "chat" messages differently from "move" messages without a separate channel subscription.
Both arrive on the same channel, but your application can route them to different handlers based on the event name. The message `id` is generated by Hotsock.

:::tip When should you use a different channel instead of a different event?
Use channels to filter the data a client needs. All events published to a channel are received by each subscriber, regardless of whether or not the client cares about every event.
:::tip[When should you use a different channel instead of a different event?]
All events published to a channel are delivered to every subscriber, regardless of whether the client cares about that event. If you find yourself sending many events that most subscribers ignore, consider splitting those into a separate channel.

Avoid scenarios where you're sending lots of events to clients where most clients are ignoring those events.

In these cases, it may be best to move those events to a separate channel and have clients that care about those events subscribe an additional channel.
Use **channels** to filter what data a client receives. Use **events** to categorize different message types within the same context.
:::

## Channel Types

There are 2 types of channels: [standard](./standard.mdx) and [presence](./presence.mdx).
| Type | Use case | Member awareness |
| -------------------------- | ----------------------------------------------------------------------------------- | ---------------- |
| [Standard](./standard.mdx) | Most situations — delivering real-time updates to clients | No |
| [Presence](./presence.mdx) | Chat rooms, collaboration, multiplayer — clients need to know who else is connected | Yes |

Both channel types support the same messaging features ([publishing](../server-api/publish-messages.mdx), [client messages](./client-messages.mdx), [storage](./storage.mdx), [message history](../connections/claims.mdx#channels.historyStart), etc.). Presence channels add member tracking on top.
Loading