diff --git a/lib/identity-search/identity-search-context.js b/lib/identity-search/identity-search-context.js index 81cd95771..944f2eb48 100644 --- a/lib/identity-search/identity-search-context.js +++ b/lib/identity-search/identity-search-context.js @@ -1,186 +1,206 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { identitySearchURL } from '../facts/identity-search.js'; import { identitySearchHeartbeatTimeout } from '../shared/timeouts.js'; import type { IdentitySearchAuthMessage } from '../types/identity-search/auth-message-types.js'; import { type IdentitySearchMessageToClient, identitySearchMessageToClientTypes, identitySearchMessageToServerTypes, identitySearchMessageToClientValidator, } from '../types/identity-search/messages.js'; import type { Heartbeat } from '../types/websocket/heartbeat-types.js'; import { useGetIdentitySearchAuthMessage } from '../utils/identity-search-utils.js'; export type IdentitySearchSocketListener = ( message: IdentitySearchMessageToClient, ) => mixed; type IdentitySearchContextType = { +addListener: (listener: IdentitySearchSocketListener) => void, +removeListener: (listener: IdentitySearchSocketListener) => void, +connected: boolean, }; const IdentitySearchContext: React.Context = React.createContext(); type Props = { +children: React.Node, }; function IdentitySearchProvider(props: Props): React.Node { const { children } = props; const [connected, setConnected] = React.useState(false); const listeners = React.useRef>(new Set()); const getIdentitySearchAuthMessage = useGetIdentitySearchAuthMessage(); const [identitySearchAuthMessage, setIdentitySearchAuthMessage] = React.useState(null); const socket = React.useRef(null); const heartbeatTimeoutID = React.useRef(); + const previousAuthMessage = React.useRef( + identitySearchAuthMessage, + ); + const stopHeartbeatTimeout = React.useCallback(() => { if (heartbeatTimeoutID.current) { clearTimeout(heartbeatTimeoutID.current); heartbeatTimeoutID.current = null; } }, []); const resetHeartbeatTimeout = React.useCallback(() => { stopHeartbeatTimeout(); heartbeatTimeoutID.current = setTimeout(() => { socket.current?.close(); setConnected(false); }, identitySearchHeartbeatTimeout); }, [stopHeartbeatTimeout]); React.useEffect(() => { void (async () => { const newAuthMessage = await getIdentitySearchAuthMessage(); setIdentitySearchAuthMessage(newAuthMessage); })(); }, [getIdentitySearchAuthMessage]); React.useEffect(() => { + const isSocketActive = + socket.current?.readyState === WebSocket.OPEN || + socket.current?.readyState === WebSocket.CONNECTING; + + const identitySearchAuthMessageChanged = + identitySearchAuthMessage !== previousAuthMessage.current; + previousAuthMessage.current = identitySearchAuthMessage; + + if ( + (!identitySearchAuthMessage || identitySearchAuthMessageChanged) && + isSocketActive + ) { + socket.current?.close(); + return; + } + if (connected || !identitySearchAuthMessage) { return; } const identitySearchSocket = new WebSocket(identitySearchURL); identitySearchSocket.onopen = () => { identitySearchSocket.send(JSON.stringify(identitySearchAuthMessage)); }; identitySearchSocket.onclose = () => { setConnected(false); }; identitySearchSocket.onerror = e => { setConnected(false); console.log('Identity Search socket error', e.message); }; identitySearchSocket.onmessage = (event: MessageEvent) => { if (typeof event.data !== 'string') { console.log('socket received a non-string message'); return; } let rawMessage; try { rawMessage = JSON.parse(event.data); } catch (e) { console.log('error while parsing Identity Search message:', e.message); return; } if (!identitySearchMessageToClientValidator.is(rawMessage)) { console.log('invalid Identity Search message'); return; } const message: IdentitySearchMessageToClient = rawMessage; resetHeartbeatTimeout(); for (const listener of listeners.current) { listener(message); } if ( message.type === identitySearchMessageToClientTypes.CONNECTION_INITIALIZATION_RESPONSE ) { if (message.status.type === 'Success' && !connected) { setConnected(true); } else if (message.status.type === 'Success' && connected) { console.log( 'received ConnectionInitializationResponse with status:', 'Success for already connected socket', ); } else { setConnected(false); console.log( 'creating session with Identity Search error:', message.status.data, ); } } else if ( message.type === identitySearchMessageToClientTypes.HEARTBEAT ) { const heartbeat: Heartbeat = { type: identitySearchMessageToServerTypes.HEARTBEAT, }; socket.current?.send(JSON.stringify(heartbeat)); } }; socket.current = identitySearchSocket; }, [ connected, identitySearchAuthMessage, resetHeartbeatTimeout, stopHeartbeatTimeout, ]); const addListener = React.useCallback( (listener: IdentitySearchSocketListener) => { listeners.current.add(listener); }, [], ); const removeListener = React.useCallback( (listener: IdentitySearchSocketListener) => { listeners.current.delete(listener); }, [], ); const value: IdentitySearchContextType = React.useMemo( () => ({ connected, addListener, removeListener, }), [connected, addListener, removeListener], ); return ( {children} ); } function useIdentitySearch(): IdentitySearchContextType { const context = React.useContext(IdentitySearchContext); invariant(context, 'IdentitySearchContext not found'); return context; } export { IdentitySearchProvider, useIdentitySearch };