feat(messaging-ui): presence + live-unread adapter seams
Add optional MessagingAdapter.setFocus(threadId) and subscribeActivity(cb) so hosts can report the foregrounded thread (backend suppresses push for it) and drive live conversation-list refresh on any thread's activity. Messenger wires focus/blur presence + activity-driven refetch. Published as 0.1.7. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@insignia/iios-messaging-ui",
|
"name": "@insignia/iios-messaging-ui",
|
||||||
"version": "0.1.6",
|
"version": "0.1.7",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"module": "dist/index.js",
|
"module": "dist/index.js",
|
||||||
|
|||||||
@@ -39,6 +39,15 @@ export interface MessagingAdapter {
|
|||||||
|
|
||||||
markRead(threadId: string, messageId: string): Promise<void>;
|
markRead(threadId: string, messageId: string): Promise<void>;
|
||||||
|
|
||||||
|
/** Report which thread is in the foreground (null when none/blurred) so the backend can suppress
|
||||||
|
* push notifications for a thread you're actively viewing. Absent => presence isn't tracked. */
|
||||||
|
setFocus?(threadId: string | null): void;
|
||||||
|
|
||||||
|
/** Subscribe to activity across ALL of the caller's threads (not just the open one) — fires for
|
||||||
|
* every incoming message. Drives live unread + in-app notifications. Absent => no cross-thread
|
||||||
|
* awareness. Returns an unsubscribe fn. */
|
||||||
|
subscribeActivity?(cb: (e: { threadId: string; message: Message }) => void): Unsubscribe;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current user's actor id, or null if not yet known.
|
* The current user's actor id, or null if not yet known.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -40,6 +40,29 @@ export function Messenger({ focusThreadId }: { focusThreadId?: string | null } =
|
|||||||
setSelected(focusThreadId);
|
setSelected(focusThreadId);
|
||||||
}, [focusThreadId]);
|
}, [focusThreadId]);
|
||||||
|
|
||||||
|
// Presence: tell the backend which thread is foregrounded so it suppresses push for it. Null while
|
||||||
|
// browsing/composing, when the window is blurred, and on unmount (leaving the messenger).
|
||||||
|
useEffect(() => {
|
||||||
|
const active = browsing || composing ? null : selected;
|
||||||
|
adapter.setFocus?.(active);
|
||||||
|
const onBlur = (): void => adapter.setFocus?.(null);
|
||||||
|
const onFocus = (): void => adapter.setFocus?.(active);
|
||||||
|
window.addEventListener('blur', onBlur);
|
||||||
|
window.addEventListener('focus', onFocus);
|
||||||
|
return () => {
|
||||||
|
adapter.setFocus?.(null);
|
||||||
|
window.removeEventListener('blur', onBlur);
|
||||||
|
window.removeEventListener('focus', onFocus);
|
||||||
|
};
|
||||||
|
}, [adapter, selected, browsing, composing]);
|
||||||
|
|
||||||
|
// Live unread + ordering: refresh the conversation list when any of the caller's threads gets a
|
||||||
|
// message (not just the open one).
|
||||||
|
useEffect(() => {
|
||||||
|
if (!adapter.subscribeActivity) return;
|
||||||
|
return adapter.subscribeActivity(() => refetch());
|
||||||
|
}, [adapter, refetch]);
|
||||||
|
|
||||||
const channels = useMemo(() => conversations.filter((c) => c.membership === 'channel'), [conversations]);
|
const channels = useMemo(() => conversations.filter((c) => c.membership === 'channel'), [conversations]);
|
||||||
const dms = useMemo(() => conversations.filter((c) => c.membership !== 'channel'), [conversations]);
|
const dms = useMemo(() => conversations.filter((c) => c.membership !== 'channel'), [conversations]);
|
||||||
const selectedConversation = useMemo(() => conversations.find((c) => c.threadId === selected) ?? null, [conversations, selected]);
|
const selectedConversation = useMemo(() => conversations.find((c) => c.threadId === selected) ?? null, [conversations, selected]);
|
||||||
|
|||||||
Reference in New Issue
Block a user