Page MenuHomePhabricator

D10938.id37554.diff
No OneTemporary

D10938.id37554.diff

diff --git a/lib/facts/identity-search.js b/lib/facts/identity-search.js
new file mode 100644
--- /dev/null
+++ b/lib/facts/identity-search.js
@@ -0,0 +1,9 @@
+// @flow
+
+import { isDev } from '../utils/dev-utils.js';
+
+const identitySearchURL: string = isDev
+ ? 'wss://identity.staging.commtechnologies.org:51004'
+ : 'wss://identity.commtechnologies.org:51004';
+
+export { identitySearchURL };
diff --git a/lib/identity-search/identity-search-context.js b/lib/identity-search/identity-search-context.js
new file mode 100644
--- /dev/null
+++ b/lib/identity-search/identity-search-context.js
@@ -0,0 +1,186 @@
+// @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<?IdentitySearchContextType> =
+ React.createContext<?IdentitySearchContextType>();
+
+type Props = {
+ +children: React.Node,
+};
+
+function IdentitySearchProvider(props: Props): React.Node {
+ const { children } = props;
+ const [connected, setConnected] = React.useState(false);
+ const listeners = React.useRef<Set<IdentitySearchSocketListener>>(new Set());
+ const getIdentitySearchAuthMessage = useGetIdentitySearchAuthMessage();
+ const [identitySearchAuthMessage, setIdentitySearchAuthMessage] =
+ React.useState<?IdentitySearchAuthMessage>(null);
+ const socket = React.useRef<?WebSocket>(null);
+ const heartbeatTimeoutID = React.useRef<?TimeoutID>();
+
+ 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(() => {
+ 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 (
+ <IdentitySearchContext.Provider value={value}>
+ {children}
+ </IdentitySearchContext.Provider>
+ );
+}
+
+function useIdentitySearch(): IdentitySearchContextType {
+ const context = React.useContext(IdentitySearchContext);
+ invariant(context, 'IdentitySearchContext not found');
+
+ return context;
+}
+
+export { IdentitySearchProvider, useIdentitySearch };
diff --git a/lib/shared/timeouts.js b/lib/shared/timeouts.js
--- a/lib/shared/timeouts.js
+++ b/lib/shared/timeouts.js
@@ -36,3 +36,7 @@
// Time after which the client consider the Tunnelbroker connection
// as unhealthy and chooses to close the socket.
export const tunnelbrokerHeartbeatTimeout = 9000; // in milliseconds
+
+// Time after which the client consider the Identity Search connection
+// as unhealthy and chooses to close the socket.
+export const identitySearchHeartbeatTimeout = 9000; // in milliseconds
diff --git a/lib/utils/identity-search-utils.js b/lib/utils/identity-search-utils.js
new file mode 100644
--- /dev/null
+++ b/lib/utils/identity-search-utils.js
@@ -0,0 +1,36 @@
+// @flow
+
+import invariant from 'invariant';
+import * as React from 'react';
+
+import { IdentityClientContext } from '../shared/identity-client-context.js';
+import { type IdentitySearchAuthMessage } from '../types/identity-search/auth-message-types.js';
+
+export function useGetIdentitySearchAuthMessage(): () => Promise<?IdentitySearchAuthMessage> {
+ const identityContext = React.useContext(IdentityClientContext);
+ invariant(identityContext, 'Identity context should be set');
+ const getAuthMetadata = identityContext.getAuthMetadata;
+
+ return React.useCallback(async () => {
+ if (!getAuthMetadata) {
+ return null;
+ }
+
+ const authMetadata = await getAuthMetadata();
+
+ if (
+ !authMetadata.userID ||
+ !authMetadata.deviceID ||
+ !authMetadata.accessToken
+ ) {
+ throw new Error('Auth metadata is incomplete');
+ }
+
+ return {
+ type: 'IdentitySearchAuthMessage',
+ userID: authMetadata?.userID,
+ deviceID: authMetadata?.deviceID,
+ accessToken: authMetadata?.accessToken,
+ };
+ }, [getAuthMetadata]);
+}

File Metadata

Mime Type
text/plain
Expires
Sun, Nov 17, 7:07 AM (21 h, 18 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2515845
Default Alt Text
D10938.id37554.diff (7 KB)

Event Timeline