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,9 +2,13 @@ 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 { IdentitySearchAuthMessage } from '../types/identity-search/auth-message-types.js'; import { type IdentitySearchMessageToClient, @@ -12,19 +16,42 @@ identitySearchMessageToServerTypes, identitySearchMessageToClientValidator, } from '../types/identity-search/messages.js'; +import { + type IdentitySearchQuery, + type IdentitySearchPrefix, +} from '../types/identity-search/search-query-types.js'; +import { + type IdentitySearchUser, + identitySearchResponseValidator, +} from '../types/identity-search/search-response-types.js'; import type { Heartbeat } from '../types/websocket/heartbeat-types.js'; import { useGetIdentitySearchAuthMessage } from '../utils/identity-search-utils.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: ( + usernamePrefix: string, + ) => Promise<$ReadOnlyArray>, + +addListener: (listener: IdentitySearchSocketListener) => mixed, + +removeListener: (listener: IdentitySearchSocketListener) => mixed, +connected: boolean, }; +const timeout = async (): Promise<$ReadOnlyArray> => { + await sleep(clientRequestVisualTimeout); + throw new Error('search request timed out'); +}; + const IdentitySearchContext: React.Context = React.createContext(); @@ -40,6 +67,7 @@ const [identitySearchAuthMessage, setIdentitySearchAuthMessage] = React.useState(null); const socket = React.useRef(null); + const promises = React.useRef({}); const heartbeatTimeoutID = React.useRef(); const previousAuthMessage = React.useRef( @@ -148,6 +176,21 @@ message.status.data, ); } + } else if ( + message.type === identitySearchMessageToClientTypes.SUCCESS || + message.type === identitySearchMessageToClientTypes.ERROR + ) { + if (!identitySearchResponseValidator.is(message)) { + console.log('Invalid search response 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 ) { @@ -166,6 +209,38 @@ stopHeartbeatTimeout, ]); + const sendPrefixQuery: ( + usernamePrefix: string, + ) => Promise<$ReadOnlyArray> = React.useCallback( + (usernamePrefix: string) => { + if (!connected || !socket.current) { + return Promise.reject(new Error('Socket is not connected')); + } + + const queryID = uuid.v4(); + const prefixQuery: IdentitySearchPrefix = { + type: identitySearchMessageToServerTypes.IDENTITY_SEARCH_PREFIX, + prefix: usernamePrefix, + }; + + const searchQuery: IdentitySearchQuery = { + type: identitySearchMessageToServerTypes.IDENTITY_SEARCH_QUERY, + id: queryID, + searchMethod: prefixQuery, + }; + + const requestPromise: Promise<$ReadOnlyArray> = + new Promise((resolve, reject) => { + promises.current[queryID] = { resolve, reject }; + }); + + socket.current?.send(JSON.stringify(searchQuery)); + + return Promise.race([requestPromise, timeout()]); + }, + [connected], + ); + const addListener = React.useCallback( (listener: IdentitySearchSocketListener) => { listeners.current.add(listener); @@ -182,11 +257,12 @@ const value: IdentitySearchContextType = React.useMemo( () => ({ + sendPrefixQuery, connected, addListener, removeListener, }), - [connected, addListener, removeListener], + [connected, addListener, removeListener, sendPrefixQuery], ); return (