diff --git a/lib/identity-search/identity-search-context.js b/lib/identity-search/identity-search-context.js --- a/lib/identity-search/identity-search-context.js +++ b/lib/identity-search/identity-search-context.js @@ -2,24 +2,45 @@ import invariant from 'invariant'; import * as React from 'react'; +import uuid from 'uuid'; import { identitySearchURL } from '../facts/identity-search.js'; -import { identitySearchHeartbeatTimeout } from '../shared/timeouts.js'; +import { + clientRequestVisualTimeout, + identitySearchHeartbeatTimeout, +} from '../shared/timeouts.js'; import type { AuthMessage } from '../types/identity-search/auth-message-types.js'; import { type IdentitySearchMessageToClient, identitySearchMessageToClientTypes, identitySearchMessageToClientValidator, } from '../types/identity-search/messages.js'; +import { identitySearchMessageToServerTypes } from '../types/identity-search/messages.js'; +import { + type SearchQuery, + type Prefix, +} from '../types/identity-search/search-query-types.js'; +import { + type User, + searchResultValidator, +} from '../types/identity-search/search-response-types.js'; import type { Heartbeat } from '../types/websocket/heartbeat-types.js'; +import sleep from '../utils/sleep.js'; export type IdentitySearchSocketListener = ( message: IdentitySearchMessageToClient, ) => mixed; +type PromiseCallbacks = { + +resolve: (hits: $ReadOnlyArray) => void, + +reject: (error: string) => void, +}; +type Promises = { [queryID: string]: PromiseCallbacks }; + type IdentitySearchContextType = { - +addListener: (listener: IdentitySearchSocketListener) => void, - +removeListener: (listener: IdentitySearchSocketListener) => void, + +sendPrefixQuery: (prefix_query: string) => mixed, + +addListener: (listener: IdentitySearchSocketListener) => mixed, + +removeListener: (listener: IdentitySearchSocketListener) => mixed, +connected: boolean, }; @@ -36,6 +57,7 @@ const [connected, setConnected] = React.useState(false); const listeners = React.useRef>(new Set()); const socket = React.useRef(null); + const promises = React.useRef({}); const heartbeatTimeoutID = React.useRef(); const previousAuthMessage = React.useRef(authMessage); @@ -129,6 +151,20 @@ message.status.data, ); } + } else if ( + message.type === identitySearchMessageToClientTypes.SUCCESS || + message.type === identitySearchMessageToClientTypes.ERROR + ) { + if (!searchResultValidator.is(message)) { + return; + } + if (message.type === identitySearchMessageToClientTypes.SUCCESS) { + promises.current[message.data.id]?.resolve(message.data.hits); + delete promises.current[message.data.id]; + } else { + promises.current[message.data.id]?.reject(message.data.error); + delete promises.current[message.data.id]; + } } else if ( message.type === identitySearchMessageToClientTypes.HEARTBEAT ) { @@ -149,6 +185,44 @@ isSocketActive, ]); + const sendPrefixQuery: (usernamePrefix: string) => mixed = React.useCallback( + (usernamePrefix: string) => { + invariant( + connected && socket.current, + 'Identity Search socket not connected for query', + ); + + const queryID = uuid.v4(); + const prefixQuery: Prefix = { + type: identitySearchMessageToServerTypes.PREFIX, + prefix: usernamePrefix, + }; + + const searchQuery: SearchQuery = { + type: identitySearchMessageToServerTypes.SEARCH_QUERY, + id: queryID, + searchMethod: prefixQuery, + }; + + const requestPromise: Promise = new Promise((resolve, reject) => { + promises.current[queryID] = { resolve, reject }; + socket.current?.send(JSON.stringify(searchQuery)); + }); + + const timeoutPromise: Promise = sleep( + clientRequestVisualTimeout, + ).then(() => { + if (promises.current[queryID]) { + promises.current[queryID].reject('Request timed out'); + delete promises.current[queryID]; + } + }); + + return Promise.race([requestPromise, timeoutPromise]); + }, + [connected], + ); + const addListener = React.useCallback( (listener: IdentitySearchSocketListener) => { listeners.current.add(listener); @@ -165,11 +239,12 @@ const value: IdentitySearchContextType = React.useMemo( () => ({ + sendPrefixQuery, connected, addListener, removeListener, }), - [connected, addListener, removeListener], + [connected, addListener, removeListener, sendPrefixQuery], ); return (