Auction Audience and Presence¶
Auction access has three durable meanings:
| State | Meaning |
|---|---|
| Viewer | An eligible subscriber currently watching. Payment is not required. |
| Participant | A subscriber with a paid cycle invoice who explicitly joined. Admission survives disconnects, rescheduling, and payment reversal until the auction ends. |
| Bidder | A participant with at least one accepted bid. Joining never requires bidding. |
Each browser tab, device, or socket creates its own AuctionPresenceSession. Counts are based on distinct enrollments, so multiple tabs do not inflate the audience.
Subscriber privacy¶
auction.audience.snapshot gives subscriber connections only these aggregate fields:
Snapshots never include viewer or participant identities. The current auction.audience.changed contract includes the affected participant object in both public and staff-scoped copies, so subscriber room members can observe that single presence transition.
auction.audience.snapshot¶
Sent to the subscribing connection after auction.subscribe. For subscribers, the payload contains aggregate counts only. Staff snapshots can additionally contain onlineSubscribers for the auction console.
auction.audience.changed¶
Sent whenever online/floor presence or a viewer-to-participant upgrade changes the audience. Both public and staff-scoped copies contain aggregate counts and the affected participant:
{
"type": "auction.audience.changed",
"data": {
"auctionId": "auction-id",
"cycleId": "cycle-id",
"occurredAt": "2026-09-06T09:50:00.000Z",
"payload": {
"viewerCount": 13,
"joinedParticipantCount": 9,
"onlineParticipantCount": 7,
"bidderCount": 3,
"participant": {
"subscriberId": "subscriber-id",
"enrolledSubscriberId": "enrollment-id",
"name": "Annie Mohan",
"avatar": null,
"accessMode": "PARTICIPANT",
"lastSeenAt": "2026-09-06T09:50:00.000Z",
"connectionCount": 2,
"source": "ONLINE"
}
}
}
}
source is ONLINE or FLOOR (AuctionParticipationSource). A floor-backed subscriber with an open socket remains FLOOR; that socket is represented as a viewer, not as an online participant.
Session audit¶
The server persists one session per WebSocket connection with its initial and current access modes, connected/last-seen/disconnected timestamps, participant upgrade time, and disconnect reason. Reasons are UNSUBSCRIBE, SOCKET_CLOSE, HEARTBEAT_EXPIRED, AUCTION_ENDED, or SERVER_SHUTDOWN.
Company auctioneers can read timestamps and reasons through:
Super-admins use the corresponding /v2/admin/... route and may also receive stored IP and user-agent values. Company responses redact those two fields. The response includes paginated sessions and immutable participations audit records. Every item in both arrays includes nullable subscriberName and subscriberAvatar. Filters include enrolledSubscriberId, accessMode, and activeOnly.
Heartbeats and recovery¶
Redis tracks every connection with a 60-second expiry. Heartbeats refresh Redis on every heartbeat and persist lastSeenAt at a throttled interval. A global batched expiry index lets any backend instance close expired database sessions as HEARTBEAT_EXPIRED. Presence remains ephemeral: clients replace it from the next snapshot after reconnecting.
Floor presence¶
Staff can update physical presence:
{
"type": "auction.presence.floor.update",
"data": {
"auctionId": "auction-id",
"enrolledSubscriberId": "enrollment-id",
"present": true
}
}
The server derives all access modes. Clients cannot supply VIEWER or PARTICIPANT.
Marking an online participant present switches the durable source to FLOOR and downgrades open sockets to viewing-only. Floor and online participation are mutually exclusive, although viewing remains available. Marking the floor presence absent does not restore online participation.