feat: live presence, delivery-tick sync, brand rollout, call fixes, desktop 0.1.3
Live presence (fixes stale in-call/status until refresh — impossible in apps): - server broadcasts a user's status over the chat socket on connect/disconnect, call join/leave, and status change (chat.js broadcastPresence; signaling + routes hooks). - client onPresence() updates the contact dot + open-chat header live. Chat delivery ticks (#6): chat-list row now mirrors the thread (delivered→double grey, read→blue) via a new 'with' field on the delivered relay + onChatRead/onChatDelivered. Call fixes: no bogus 'host handed over' when a 1:1 call ends (leaveMeeting forced); branded call-connecting + chat-thread loaders; header subtitle tracks live call state. Notifications: web notify + sw.js use sender/group DP + brand icon (not old wordmark); desktop shell drops Web Push so only the single native toast fires (#5). Brand: master icon/splash/loaders wired everywhere (PWA/favicon/apple-touch/.ico), branded login (blue + gold CTA), branded toasts (BZToast) on all pages, Electron splash. Desktop: dev auto-targets localhost (packaged→prod); version 0.1.3 with new multi-size icon; dropped unused node-notifier; removed home-mockup.html. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+26
-2
@@ -1,7 +1,8 @@
|
||||
// Chat presence + real-time delivery. A logged-in user opens a WebSocket and sends
|
||||
// `chat-hello`; signaling.js registers the socket here. Messages are persisted over HTTP
|
||||
// (routes.js) and pushed live to the recipient's sockets via pushToUser().
|
||||
const { chatClients } = require('./presence');
|
||||
const { chatClients, meetingRooms } = require('./presence');
|
||||
let _repos = null; const repos = () => (_repos || (_repos = require('./repos'))); // lazy: avoid a require cycle
|
||||
|
||||
function register(userId, ws) {
|
||||
if (!chatClients.has(userId)) chatClients.set(userId, new Set());
|
||||
@@ -28,4 +29,27 @@ function pushToUser(userId, obj) {
|
||||
for (const ws of s) { if (ws.readyState === 1) { try { ws.send(data); } catch (_) {} } }
|
||||
}
|
||||
|
||||
module.exports = { register, unregister, isOnline, pushToUser };
|
||||
// --- Live presence -------------------------------------------------------------------------
|
||||
// A user's effective status (online / in-a-call / away / …) changes with no HTTP round-trip:
|
||||
// they connect or disconnect a socket, or join/leave a call. Without pushing that change, OTHER
|
||||
// users only see it after a full page reload — impossible in the desktop/mobile apps. So whenever
|
||||
// it changes we broadcast the user's fresh status to everyone else's sockets, and the client
|
||||
// updates that contact's dot/subtitle in place.
|
||||
function isInCall(userId) {
|
||||
for (const [, peers] of meetingRooms) { for (const [, p] of peers) { if (p.ws && p.ws._meetingUserId === userId) return true; } }
|
||||
return false;
|
||||
}
|
||||
function effectiveStatus(userId) {
|
||||
if (isInCall(userId)) return 'incall'; // derived (overrides the stored status)
|
||||
try { const u = repos().users.byId(userId); return (u && u.status) || 'active'; } catch (_) { return 'active'; }
|
||||
}
|
||||
function broadcastPresence(userId) {
|
||||
if (!userId) return;
|
||||
const payload = JSON.stringify({ type: 'presence', userId, online: isOnline(userId), status: effectiveStatus(userId) });
|
||||
for (const [uid, set] of chatClients) {
|
||||
if (uid === userId) continue; // no need to tell someone about their own status
|
||||
for (const ws of set) { if (ws.readyState === 1) { try { ws.send(payload); } catch (_) {} } }
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { register, unregister, isOnline, pushToUser, broadcastPresence };
|
||||
|
||||
Reference in New Issue
Block a user