@@ -14,33 +14,24 @@ import {
1414
1515// ── Environment config ───────────────────────────────────────────────────────
1616
17- const WS_URL : string =
18- ( typeof import . meta !== "undefined" &&
19- ( import . meta as any ) . env ?. VITE_HOCUSPOCUS_URL ) ||
20- ( typeof globalThis !== "undefined" &&
21- ( globalThis as any ) . __HOCUSPOCUS_URL__ ) ||
22- "ws://localhost:3006" ;
23-
24- const AUTH_TOKEN : string =
25- ( typeof import . meta !== "undefined" &&
26- ( import . meta as any ) . env ?. VITE_HOCUSPOCUS_SECRET ) ||
27- "" ;
17+ const WS_URL = REACT_APP_HOCUSPOCUS_URL || "ws://localhost:3006" ;
2818
29- type ConnectionState = "connecting" | "open" | "closed " ;
19+ const AUTH_TOKEN = REACT_APP_HOCUSPOCUS_SECRET | | "" ;
3020
31- function mapWebSocketStatus ( status ?: string ) : ConnectionState {
32- if ( status === WebSocketStatus . Connected ) {
33- return "open" ;
34- }
21+ type ConnectionState = "connecting" | "open" | "closed" ;
3522
36- if ( status === WebSocketStatus . Connecting ) {
37- return "connecting" ;
23+ function mapWebSocketStatus ( status ?: WebSocketStatus ) : ConnectionState {
24+ switch ( status ) {
25+ case WebSocketStatus . Connected :
26+ return "open" ;
27+ case WebSocketStatus . Connecting :
28+ return "connecting" ;
29+ default :
30+ return "closed" ;
3831 }
39-
40- return "closed" ;
4132}
4233
43- // ── Context ──────────────────────────────────────────────────────────────────
34+ // ── Context ────────────────────────────────────────────────────────────────────
4435
4536interface HocuspocusContextValue {
4637 provider : HocuspocusProvider ;
@@ -65,21 +56,19 @@ interface HocuspocusRoomProviderProps {
6556 /** Document/room name — all clients with the same name share state. */
6657 room : string ;
6758 /** Initial presence fields to set on connect. */
68- initialPresence ?: Record < string , any > ;
59+ initialPresence ?: Record < string , unknown > ;
6960 /** Called when auth fails. */
70- onAuthenticationFailed ?: ( error : any ) => void ;
61+ onAuthenticationFailed ?: ( error : unknown ) => void ;
7162 children : React . ReactNode ;
7263}
7364
74- function HocuspocusRoomProviderInner ( {
65+ export function HocuspocusRoomProvider ( {
7566 room,
7667 initialPresence,
7768 onAuthenticationFailed,
7869 children,
7970} : HocuspocusRoomProviderProps ) {
80- const stableInitialPresence = useMemo ( ( ) => initialPresence ?? null , [
81- JSON . stringify ( initialPresence ?? null ) ,
82- ] ) ;
71+ const initialPresenceKey = JSON . stringify ( initialPresence ?? null ) ;
8372
8473 const value = useMemo ( ( ) => {
8574 const doc = new Y . Doc ( ) ;
@@ -88,7 +77,7 @@ function HocuspocusRoomProviderInner({
8877 name : room ,
8978 document : doc ,
9079 token : AUTH_TOKEN || undefined ,
91- onAuthenticationFailed : ( data : any ) => {
80+ onAuthenticationFailed : ( data : unknown ) => {
9281 console . error ( "[Hocuspocus] Auth failed:" , data ) ;
9382 onAuthenticationFailed ?.( data ) ;
9483 } ,
@@ -100,10 +89,10 @@ function HocuspocusRoomProviderInner({
10089 } , [ room ] ) ;
10190
10291 useEffect ( ( ) => {
103- if ( stableInitialPresence ) {
104- value . provider . setAwarenessField ( "user" , stableInitialPresence ) ;
92+ if ( initialPresenceKey !== "null" ) {
93+ value . provider . setAwarenessField ( "user" , JSON . parse ( initialPresenceKey ) ) ;
10594 }
106- } , [ stableInitialPresence , value . provider ] ) ;
95+ } , [ initialPresenceKey , value . provider ] ) ;
10796
10897 useEffect ( ( ) => {
10998 return ( ) => {
@@ -112,119 +101,127 @@ function HocuspocusRoomProviderInner({
112101 } ;
113102 } , [ value ] ) ;
114103
115- return React . createElement ( HocuspocusContext . Provider , { value } , children ) ;
104+ return (
105+ < HocuspocusContext . Provider value = { value } >
106+ { children }
107+ </ HocuspocusContext . Provider >
108+ ) ;
116109}
117110
118- export const HocuspocusRoomProvider = HocuspocusRoomProviderInner ;
119-
120111// ── Hook: useConnection ──────────────────────────────────────────────────────
121112
122113export function useConnection ( ) : { state : ConnectionState } {
123114 const { provider } = useHocuspocusContext ( ) ;
124- const [ state , setState ] = useState < ConnectionState > ( ( ) =>
125- mapWebSocketStatus ( provider . configuration . websocketProvider . status ) ,
115+
116+ const getStatus = useCallback (
117+ ( ) => mapWebSocketStatus ( provider . configuration . websocketProvider . status ) ,
118+ [ provider ] ,
126119 ) ;
127120
121+ const [ state , setState ] = useState < ConnectionState > ( getStatus ) ;
122+
128123 useEffect ( ( ) => {
129- const sync = ( ) => {
130- setState ( mapWebSocketStatus ( provider . configuration . websocketProvider . status ) ) ;
131- } ;
124+ // Sync immediately when provider changes
125+ setState ( getStatus ( ) ) ;
132126
133- const onStatus = ( ) => {
134- sync ( ) ;
127+ const handleStatus = ( { status } : { status : WebSocketStatus } ) => {
128+ setState ( mapWebSocketStatus ( status ) ) ;
135129 } ;
136130
137- sync ( ) ;
138- provider . on ( "status" , onStatus ) ;
139- provider . on ( "connect" , onStatus ) ;
140- provider . on ( "disconnect" , onStatus ) ;
131+ provider . on ( "status" , handleStatus ) ;
141132
142133 return ( ) => {
143- provider . off ( "status" , onStatus ) ;
144- provider . off ( "connect" , onStatus ) ;
145- provider . off ( "disconnect" , onStatus ) ;
134+ provider . off ( "status" , handleStatus ) ;
146135 } ;
147- } , [ provider ] ) ;
136+ } , [ provider , getStatus ] ) ;
148137
149138 return { state } ;
150139}
151140
152141// ── Hook: useMyPresence ──────────────────────────────────────────────────────
153142
154143export function useMyPresence ( ) : [
155- Record < string , any > ,
156- ( fields : Record < string , any > ) => void ,
144+ Record < string , unknown > ,
145+ ( fields : Record < string , unknown > ) => void ,
157146] {
158147 const { provider } = useHocuspocusContext ( ) ;
159148
160- const [ presence , setPresenceState ] = useState < Record < string , any > > (
149+ const getPresence = useCallback (
161150 ( ) => provider . awareness ?. getLocalState ( ) ?. user ?? { } ,
151+ [ provider ] ,
162152 ) ;
163153
164- const setPresence = useCallback (
165- ( fields : Record < string , any > ) => {
154+ const [ presence , setPresence ] = useState < Record < string , unknown > > ( getPresence ) ;
155+
156+ const updatePresence = useCallback (
157+ ( fields : Record < string , unknown > ) => {
166158 provider . setAwarenessField ( "user" , fields ) ;
167- setPresenceState ( fields ) ;
159+ setPresence ( fields ) ;
168160 } ,
169161 [ provider ] ,
170162 ) ;
171163
172164 useEffect ( ( ) => {
173165 const awareness = provider . awareness ;
174- if ( ! awareness ) {
175- return ;
176- }
166+ if ( ! awareness ) return ;
177167
178- const sync = ( ) => {
179- setPresenceState ( awareness . getLocalState ( ) ?. user ?? { } ) ;
168+ const handleChange = ( ) => {
169+ setPresence ( getPresence ( ) ) ;
180170 } ;
181171
182- sync ( ) ;
183- awareness . on ( "change" , sync ) ;
172+ awareness . on ( "change" , handleChange ) ;
184173
185174 return ( ) => {
186- awareness . off ( "change" , sync ) ;
175+ awareness . off ( "change" , handleChange ) ;
187176 } ;
188- } , [ provider ] ) ;
177+ } , [ provider , getPresence ] ) ;
189178
190- return [ presence , setPresence ] ;
179+ return [ presence , updatePresence ] ;
191180}
192181
193182// ── Hook: useOthers ──────────────────────────────────────────────────────────
194183
195184interface OtherUser {
196185 clientId : number ;
197- [ key : string ] : any ;
186+ presence : Record < string , unknown > ;
198187}
199188
200189export function useOthers ( ) : OtherUser [ ] {
201190 const { provider } = useHocuspocusContext ( ) ;
202- const [ others , setOthers ] = useState < OtherUser [ ] > ( [ ] ) ;
191+
192+ const getOthers = useCallback ( ( ) : OtherUser [ ] => {
193+ const awareness = provider . awareness ;
194+ if ( ! awareness ) return [ ] ;
195+
196+ const localClientId = awareness . clientID ;
197+ const others : OtherUser [ ] = [ ] ;
198+
199+ awareness . getStates ( ) . forEach ( ( state : Record < string , unknown > , clientId : number ) => {
200+ if ( clientId === localClientId ) return ;
201+ if ( state ?. user ) {
202+ others . push ( { clientId, presence : state . user as Record < string , unknown > } ) ;
203+ }
204+ } ) ;
205+
206+ return others ;
207+ } , [ provider ] ) ;
208+
209+ const [ others , setOthers ] = useState < OtherUser [ ] > ( getOthers ) ;
203210
204211 useEffect ( ( ) => {
205212 const awareness = provider . awareness ;
206213 if ( ! awareness ) return ;
207214
208- const update = ( ) => {
209- const localClientId = awareness . clientID ;
210- const states : OtherUser [ ] = [ ] ;
211- awareness . getStates ( ) . forEach ( ( state : Record < string , any > , clientId : number ) => {
212- if ( clientId === localClientId ) return ;
213- if ( state ?. user ) {
214- states . push ( { clientId, presence : state . user } ) ;
215- }
216- } ) ;
217- setOthers ( states ) ;
215+ const handleChange = ( ) => {
216+ setOthers ( getOthers ( ) ) ;
218217 } ;
219218
220- update ( ) ;
221- awareness . on ( "change" , update ) ;
222- awareness . on ( "update" , update ) ;
219+ awareness . on ( "change" , handleChange ) ;
220+
223221 return ( ) => {
224- awareness . off ( "change" , update ) ;
225- awareness . off ( "update" , update ) ;
222+ awareness . off ( "change" , handleChange ) ;
226223 } ;
227- } , [ provider ] ) ;
224+ } , [ provider , getOthers ] ) ;
228225
229226 return others ;
230227}
@@ -235,28 +232,31 @@ export function useOthers(): OtherUser[] {
235232
236233export function useStorage (
237234 mapName : string ,
238- ) : [ Record < string , any > | null , Y . Map < any > | null ] {
235+ ) : [ Record < string , unknown > | null , Y . Map < unknown > | null ] {
239236 const { doc } = useHocuspocusContext ( ) ;
240237
241238 const yMap = useMemo ( ( ) => doc . getMap ( mapName ) , [ doc , mapName ] ) ;
242239
243- const [ snapshot , setSnapshot ] = useState < Record < string , any > | null > ( ( ) =>
244- yMap ? Object . fromEntries ( yMap . entries ( ) ) : null ,
240+ const getSnapshot = useCallback (
241+ ( ) => ( yMap ? Object . fromEntries ( yMap . entries ( ) ) : null ) ,
242+ [ yMap ] ,
245243 ) ;
246244
245+ const [ snapshot , setSnapshot ] = useState < Record < string , unknown > | null > ( getSnapshot ) ;
246+
247247 useEffect ( ( ) => {
248248 if ( ! yMap ) return ;
249249
250- const sync = ( ) => {
251- setSnapshot ( Object . fromEntries ( yMap . entries ( ) ) ) ;
250+ const handleChange = ( ) => {
251+ setSnapshot ( getSnapshot ( ) ) ;
252252 } ;
253253
254- sync ( ) ;
255- yMap . observeDeep ( sync ) ;
254+ yMap . observe ( handleChange ) ;
255+
256256 return ( ) => {
257- yMap . unobserveDeep ( sync ) ;
257+ yMap . unobserve ( handleChange ) ;
258258 } ;
259- } , [ yMap ] ) ;
259+ } , [ yMap , getSnapshot ] ) ;
260260
261261 return [ snapshot , yMap ] ;
262262}
0 commit comments