import { useState , useEffect , useContext } from 'react' ; import { HypermergeUrl , parseDocumentLink } from './ShareLink' ; import { useTimeouts , useMessaging , useRepo } from './Hooks' ; import { CurrentDeviceContext } from './components/content-types/workspace/Device' ; /**
 * heartbeats are an accumulated list of the URLs we have "open" and so should
 * report heartbeats (and forward our "presence data") to.
 */ const heartbeats = { } ; /**
 * myPresence is the data (per-url) that we send to our peers
 */ const myPresence = { } ; const HEARTBEAT_INTERVAL = 1000 ; export interface RemotePresence < P > { contact : HypermergeUrl ; device : HypermergeUrl ; data ? : P ; } export interface RemotePresenceCache < P > { [ contactAndDevice ] : RemotePresence < P > ; } interface HeartbeatMessage { contact : HypermergeUrl ; device : HypermergeUrl ; heartbeat ? : boolean ; departing ? : boolean ; data ? : any ; } param contact export function useAllHeartbeats ( contact ) { const repo = useRepo ( ) ; const currentDeviceId = useContext ( CurrentDeviceContext ) ; const parsed = parseDocumentLink ( currentDeviceId || '' ) ; const device = parsed . hypermergeUrl || null ; useEffect ( ( ) => { if ( ! contact ) { return ( ) => { } ; } if ( ! device ) { return ( ) => { } ; } const interval = setInterval ( ( ) => { Object . entries ( heartbeats ) . forEach ( ( [ url , count ] ) => { if ( count > 0 ) { const msg = { contact , device , heartbeat : true , data : myPresence [ url ] } ; repo . message ( url as HypermergeUrl , msg ) ; } else { depart ( url as HypermergeUrl ) ; delete heartbeats [ url ] ; } } ) ; } , HEARTBEAT_INTERVAL ) ; function depart ( url ) { if ( ! contact || ! device ) { return ; } const departMessage = { contact , device , departing : true } ; repo . message ( url , departMessage ) ; } return ( ) => { clearInterval ( interval ) ; Object . entries ( heartbeats ) . forEach ( ( [ url ] ) => depart ( url as HypermergeUrl ) ) ; } ; } , [ contact , device ] ) ; } export function useHeartbeat ( docUrl ) { useEffect ( ( ) => { if ( ! docUrl ) { return ( ) => { } ; } heartbeats [ docUrl ] = ( heartbeats [ docUrl ] || 0 ) + 1 ; return ( ) => { heartbeats [ docUrl ] && ( heartbeats [ docUrl ] -= 1 ) ; } ; } , [ docUrl ] ) ; } function remotePresenceToLookupKey < T > ( presence ) { return `${ presence . contact }-${ presence . device }` ; } function lookupKeyToPresencePieces ( key ) { const [ contact , device ] = key . split ( '-' ) ; return [ contact as HypermergeUrl , device as HypermergeUrl ] ; } export function usePresence < P > ( url , presence ? , key = '/' ) { const [ remote , setRemoteInner ] = useState < RemotePresenceCache < P > > ( { } ) ; const setSingleRemote = ( presence ) => { setRemoteInner ( ( prev ) => ( { ... prev , [ remotePresenceToLookupKey ( presence ) ] : { ... presence } } ) ) ; } ; const [ bumpTimeout , depart ] = useTimeouts ( 5000 , ( key ) => { const [ contact , device ] = lookupKeyToPresencePieces ( key ) ; setSingleRemote ( { contact , device , data : undefined } ) ; } ) ; useMessaging < any > ( url , ( msg ) => { const { contact , device , heartbeat , departing , data } = msg ; const presence = { contact , device , data } ; if ( heartbeat || data ) { bumpTimeout ( remotePresenceToLookupKey ( presence ) ) ; setSingleRemote ( presence ) ; } else if ( departing ) { depart ( remotePresenceToLookupKey ( presence ) ) ; } } ) ; useEffect ( ( ) => { if ( ! url || ! key ) return ( ) => { } ; if ( ! myPresence [ url ] ) { myPresence [ url ] = { } ; } if ( presence === undefined ) { delete myPresence [ url ] [ key ] ; } else { myPresence [ url ] [ key ] = presence ; } return ( ) => { delete myPresence [ url ] [ key ] ; } ; } , [ key , presence ] ) ; return Object . values ( remote ) . filter ( ( presence ) => presence . data ) . map ( ( presence ) => ( { ... presence , data : presence . data ! [ key ] } ) ) ; }