Offline outbox
Point-of-sale terminals, field-service apps and warehouse scanners run on Wi-Fi that comes and goes, and their users reload, close the laptop, or lose battery. An order that only lived in memory is gone when that happens.
The client's send queue holds messages while the socket is down, but only in memory, and only until they're handed to the socket. A message sent just before the connection dropped may never have reached the server, and nothing sends it again. createWebSocketOutbox, from react-websocket/outbox, closes both gaps:
- Stored. Each message is kept in IndexedDB, so it survives a reload or a crash, and is sent when the page comes back.
- Acknowledged. It stays until the server acknowledges it, and is sent again on every new connection until then.
- Idempotent. It carries the same key every time, so the server can tell a retry from a new message.
Setup
Then add messages from anywhere:
add() stores the message and sends it right away if the socket is open, or as soon as it opens. It resolves with the message's id once it's stored. To use an id of your own, such as an order number, pass it: orders.add(order, order.number).
While messages are waiting, the outbox holds the connection open, so they're delivered even if no component uses pos.
The server's side
The server must do two things: acknowledge every message with its id, and do nothing for an id it has already handled except acknowledge it again. A message can arrive twice when the connection drops after the server handled it but before its acknowledgement got back, or when two tabs send the same stored message.
Acknowledge only once the message is safe on your side, for example after the database write commits. Until the acknowledgement arrives, the client assumes the message may be lost.
Showing what's waiting
useWebSocketOutbox lists the waiting messages and re-renders as they're acknowledged:
Each entry has its id, message, createdAt, and sent: whether it has gone out on the current connection and waits for its acknowledgement.
Messages that are too old
An order from yesterday may be worse than no order. maxAge drops a message instead of sending it once it's older than that, and onExpire lets you tell the user:
Age is checked when a message would be sent: on every new connection, and when stored messages are loaded after a reload. It uses Date.now(), so time the device spent asleep counts. For messages in the client's own queue, the same idea is queue.maxAge.
Cancelling and logging out
Things to know
- Order. Messages are sent oldest first. After a reload, nothing is sent until the stored messages are loaded, so they go before new ones.
- Tabs. Every open tab sends the stored messages it loaded. The idempotency key makes that harmless. A message acknowledged, removed or cleared in one tab is dropped in the others too, so a logout in one tab stops what the others would still send.
- No acknowledgement? Without
ack, a message is removed once it has been handed to an open socket, and one sent right away is never stored. It still survives a reload while offline, but one sent just before the connection dropped can be lost. - Not the send queue. Outbox messages don't go through the client's queue, so
queueoptions don't apply to them. They're sent before what the client queued. - Storage. The default is IndexedDB, in a database named
react-websocket-outbox:<name>. Where IndexedDB doesn't exist, as in a server render, messages are kept in memory. If storing fails, for example when the device is out of space,add()rejects, but the message is still sent from memory. Passstorageto keep messages elsewhere, ormemoryStorage()in tests. - Deploys. After
migrate(), unacknowledged messages are sent again on the new connection.
Testing
Pass storage: memoryStorage() to keep tests independent of IndexedDB, and drive the socket with a mock, as in Testing. To test persistence itself, create a second outbox on the same storage object: it loads what the first one stored, like a page after a reload.
See also
createWebSocketOutboxanduseWebSocketOutbox- Reconnecting and heartbeats: the send queue and
queue.maxAge - Chaos testing: drop connections at random and watch the outbox recover
- Build a team chat app and Build a collaborative whiteboard: the outbox for chat messages and drawings
- Build live notifications: reads that survive going offline, and Build live delivery tracking: proof of delivery from a phone with no signal