@thrillee/aegischat 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,11 +2,9 @@
2
2
  // AegisChat React SDK - useAutoRead Hook
3
3
  // ============================================================================
4
4
 
5
- import { useCallback, useEffect, useState } from 'react';
5
+ import { useCallback, useEffect, useRef } from 'react';
6
6
  import { channelsApi } from '../services/api';
7
7
 
8
- const SESSION_STORAGE_KEY = '@aegischat/activeChannel';
9
-
10
8
  export interface UseAutoReadOptions {
11
9
  onMarkAsRead?: (channelId: string) => void;
12
10
  }
@@ -14,17 +12,24 @@ export interface UseAutoReadOptions {
14
12
  export interface UseAutoReadReturn {
15
13
  markAsRead: (channelId: string) => Promise<void>;
16
14
  markAllAsRead: () => Promise<void>;
15
+ /** Returns current focus state - use this getter to avoid stale closures */
16
+ getIsFocused: () => boolean;
17
+ /** @deprecated Use getIsFocused() instead to avoid stale closures in callbacks */
17
18
  isFocused: boolean;
18
19
  }
19
20
 
20
21
  export function useAutoRead(options: UseAutoReadOptions = {}): UseAutoReadReturn {
21
- const [isFocused, setIsFocused] = useState(false);
22
+ const isFocusedRef = useRef(typeof document !== 'undefined' && document.hasFocus());
23
+ const onMarkAsReadRef = useRef(options.onMarkAsRead);
22
24
 
25
+ // Keep the callback ref updated
23
26
  useEffect(() => {
24
- setIsFocused(typeof document !== 'undefined' && document.hasFocus());
27
+ onMarkAsReadRef.current = options.onMarkAsRead;
28
+ }, [options.onMarkAsRead]);
25
29
 
26
- const handleFocus = () => setIsFocused(true);
27
- const handleBlur = () => setIsFocused(false);
30
+ useEffect(() => {
31
+ const handleFocus = () => { isFocusedRef.current = true; };
32
+ const handleBlur = () => { isFocusedRef.current = false; };
28
33
 
29
34
  window.addEventListener('focus', handleFocus);
30
35
  window.addEventListener('blur', handleBlur);
@@ -35,18 +40,22 @@ export function useAutoRead(options: UseAutoReadOptions = {}): UseAutoReadReturn
35
40
  };
36
41
  }, []);
37
42
 
43
+ const getIsFocused = useCallback(() => {
44
+ return isFocusedRef.current;
45
+ }, []);
46
+
38
47
  const markAsRead = useCallback(async (channelId: string) => {
39
- if (!isFocused) return;
48
+ if (!isFocusedRef.current) return;
40
49
  try {
41
50
  await channelsApi.markAsRead(channelId);
42
- options.onMarkAsRead?.(channelId);
51
+ onMarkAsReadRef.current?.(channelId);
43
52
  } catch (error) {
44
53
  console.error('[AegisChat] useAutoRead: Failed to mark as read:', error);
45
54
  }
46
- }, [isFocused, options.onMarkAsRead]);
55
+ }, []);
47
56
 
48
57
  const markAllAsRead = useCallback(async () => {
49
- if (!isFocused) return;
58
+ if (!isFocusedRef.current) return;
50
59
  try {
51
60
  const response = await channelsApi.list({});
52
61
  const channels = response.channels || [];
@@ -56,9 +65,15 @@ export function useAutoRead(options: UseAutoReadOptions = {}): UseAutoReadReturn
56
65
  } catch (error) {
57
66
  console.error('[AegisChat] useAutoRead: Failed to mark all as read:', error);
58
67
  }
59
- }, [isFocused]);
68
+ }, []);
60
69
 
61
- return { markAsRead, markAllAsRead, isFocused };
70
+ return {
71
+ markAsRead,
72
+ markAllAsRead,
73
+ getIsFocused,
74
+ // Keep for backwards compatibility but warn it is deprecated
75
+ isFocused: isFocusedRef.current
76
+ };
62
77
  }
63
78
 
64
79
  export default useAutoRead;
@@ -37,7 +37,7 @@ export interface UseChatOptions {
37
37
 
38
38
  initialSession?: ChatSession | null;
39
39
  autoConnect?: boolean;
40
- onMessage?: (message: Message) => void;
40
+ onMessage?: (message: Message, context: { activeChannelId: string | null }) => void;
41
41
  onTyping?: (channelId: string, user: TypingUser) => void;
42
42
  onConnectionChange?: (connected: boolean) => void;
43
43
  }
@@ -102,8 +102,12 @@ export function useChat(options: Partial<UseChatOptions> = {}): UseChatReturn {
102
102
  const [session, setSession] = useState<ChatSession | null>(null);
103
103
  const [isConnected, setIsConnected] = useState(false);
104
104
  const [isConnecting, setIsConnecting] = useState(false);
105
+ const getStoredActiveChannel = (): string | null => {
106
+ if (typeof window === "undefined") return null;
107
+ return sessionStorage.getItem(SESSION_STORAGE_KEY);
108
+ };
105
109
  const [activeChannelId, setActiveChannelIdState] = useState<string | null>(
106
- null,
110
+ getStoredActiveChannel,
107
111
  );
108
112
  const [channels, setChannels] = useState<ChannelListItem[]>([]);
109
113
  const [messages, setMessages] = useState<Message[]>([]);
@@ -128,7 +132,7 @@ export function useChat(options: Partial<UseChatOptions> = {}): UseChatReturn {
128
132
  const clientIdRef = useRef<string | undefined>(undefined);
129
133
  const autoConnectRef = useRef(true);
130
134
  const onMessageRef =
131
- useRef<(message: Message) => void | undefined>(undefined);
135
+ useRef<((message: Message, context: { activeChannelId: string | null }) => void) | undefined>(undefined);
132
136
  const onTypingRef = useRef<
133
137
  ((channelId: string, user: TypingUser) => void) | undefined
134
138
  >(undefined);
@@ -146,6 +150,7 @@ export function useChat(options: Partial<UseChatOptions> = {}): UseChatReturn {
146
150
  }, []);
147
151
 
148
152
  const setActiveChannelId = useCallback((id: string | null) => {
153
+ activeChannelIdRef.current = id;
149
154
  setActiveChannelIdState(id);
150
155
  if (typeof window !== "undefined") {
151
156
  if (id) {
@@ -218,7 +223,7 @@ export function useChat(options: Partial<UseChatOptions> = {}): UseChatReturn {
218
223
  if (prev.some((m) => m.id === newMessage.id)) return prev;
219
224
  return [...prev, { ...newMessage, status: "delivered" }];
220
225
  });
221
- onMessageRef.current?.(newMessage);
226
+ onMessageRef.current?.(newMessage, { activeChannelId: currentActiveChannelId });
222
227
  }
223
228
  setChannels((prev) => {
224
229
  const updated = prev.map((ch) =>