diff --git a/lib/actions/activity-actions.js b/lib/actions/activity-actions.js index 606eb4215..10c87ea6c 100644 --- a/lib/actions/activity-actions.js +++ b/lib/actions/activity-actions.js @@ -1,62 +1,96 @@ // @flow import type { ActivityUpdate, ActivityUpdateSuccessPayload, SetThreadUnreadStatusPayload, SetThreadUnreadStatusRequest, - SetThreadUnreadStatusResult, } from '../types/activity-types.js'; -import type { CallServerEndpoint } from '../utils/call-server-endpoint.js'; +import { extractKeyserverIDFromID } from '../utils/action-utils.js'; +import type { CallKeyserverEndpoint } from '../utils/keyserver-call'; +import { useKeyserverCall } from '../utils/keyserver-call.js'; + +export type UpdateActivityInput = { + +activityUpdates: $ReadOnlyArray, +}; const updateActivityActionTypes = Object.freeze({ started: 'UPDATE_ACTIVITY_STARTED', success: 'UPDATE_ACTIVITY_SUCCESS', failed: 'UPDATE_ACTIVITY_FAILED', }); const updateActivity = ( - callServerEndpoint: CallServerEndpoint, - ): (( - activityUpdates: $ReadOnlyArray, - ) => Promise) => - async activityUpdates => { - const response = await callServerEndpoint('update_activity', { - updates: activityUpdates, - }); + callKeyserverEndpoint: CallKeyserverEndpoint, + ): ((input: UpdateActivityInput) => Promise) => + async input => { + const { activityUpdates } = input; + const requests = {}; + for (const update of activityUpdates) { + const keyserverID = extractKeyserverIDFromID(update.threadID); + if (!requests[keyserverID]) { + requests[keyserverID] = { updates: [] }; + } + requests[keyserverID].updates.push(update); + } + + const responses = await callKeyserverEndpoint('update_activity', requests); + + let unfocusedToUnread = []; + for (const keyserverID in responses) { + unfocusedToUnread = unfocusedToUnread.concat( + responses[keyserverID].unfocusedToUnread, + ); + } + return { activityUpdates, result: { - unfocusedToUnread: response.unfocusedToUnread, + unfocusedToUnread, }, }; }; +function useUpdateActivity(): ( + input: UpdateActivityInput, +) => Promise { + return useKeyserverCall(updateActivity); +} + const setThreadUnreadStatusActionTypes = Object.freeze({ started: 'SET_THREAD_UNREAD_STATUS_STARTED', success: 'SET_THREAD_UNREAD_STATUS_SUCCESS', failed: 'SET_THREAD_UNREAD_STATUS_FAILED', }); const setThreadUnreadStatus = ( - callServerEndpoint: CallServerEndpoint, + callKeyserverEndpoint: CallKeyserverEndpoint, ): (( - request: SetThreadUnreadStatusRequest, + input: SetThreadUnreadStatusRequest, ) => Promise) => - async request => { - const response: SetThreadUnreadStatusResult = await callServerEndpoint( + async input => { + const keyserverID = extractKeyserverIDFromID(input.threadID); + const requests = { [keyserverID]: input }; + + const responses = await callKeyserverEndpoint( 'set_thread_unread_status', - request, + requests, ); return { - resetToUnread: response.resetToUnread, - threadID: request.threadID, + resetToUnread: responses[keyserverID].resetToUnread, + threadID: input.threadID, }; }; +function useSetThreadUnreadStatus(): ( + request: SetThreadUnreadStatusRequest, +) => Promise { + return useKeyserverCall(setThreadUnreadStatus); +} + export { updateActivityActionTypes, - updateActivity, + useUpdateActivity, setThreadUnreadStatusActionTypes, - setThreadUnreadStatus, + useSetThreadUnreadStatus, }; diff --git a/lib/hooks/toggle-unread-status.js b/lib/hooks/toggle-unread-status.js index 3194412b4..dad5cf202 100644 --- a/lib/hooks/toggle-unread-status.js +++ b/lib/hooks/toggle-unread-status.js @@ -1,59 +1,54 @@ // @flow import * as React from 'react'; import { - setThreadUnreadStatus, + useSetThreadUnreadStatus, setThreadUnreadStatusActionTypes, } from '../actions/activity-actions.js'; import type { SetThreadUnreadStatusPayload, SetThreadUnreadStatusRequest, } from '../types/activity-types.js'; import type { ThreadInfo } from '../types/thread-types.js'; -import { - useDispatchActionPromise, - useServerCall, -} from '../utils/action-utils.js'; +import { useDispatchActionPromise } from '../utils/action-utils.js'; function useToggleUnreadStatus( threadInfo: ThreadInfo, mostRecentNonLocalMessage: ?string, afterAction: () => void, ): () => void { const dispatchActionPromise = useDispatchActionPromise(); const { currentUser } = threadInfo; const boundSetThreadUnreadStatus: ( request: SetThreadUnreadStatusRequest, - ) => Promise = useServerCall( - setThreadUnreadStatus, - ); + ) => Promise = useSetThreadUnreadStatus(); const toggleUnreadStatus = React.useCallback(() => { const request = { threadID: threadInfo.id, unread: !currentUser.unread, latestMessage: mostRecentNonLocalMessage, }; dispatchActionPromise( setThreadUnreadStatusActionTypes, boundSetThreadUnreadStatus(request), undefined, ({ threadID: threadInfo.id, unread: !currentUser.unread, }: { +threadID: string, +unread: boolean }), ); afterAction(); }, [ threadInfo.id, currentUser.unread, mostRecentNonLocalMessage, dispatchActionPromise, afterAction, boundSetThreadUnreadStatus, ]); return toggleUnreadStatus; } export default useToggleUnreadStatus; diff --git a/lib/socket/activity-handler.react.js b/lib/socket/activity-handler.react.js index 683cbb923..f6296b4f0 100644 --- a/lib/socket/activity-handler.react.js +++ b/lib/socket/activity-handler.react.js @@ -1,135 +1,132 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { useDispatch } from 'react-redux'; import { updateActivityActionTypes, - updateActivity, + useUpdateActivity, } from '../actions/activity-actions.js'; import { connectionSelector } from '../selectors/keyserver-selectors.js'; import { getMostRecentNonLocalMessageID } from '../shared/message-utils.js'; import { threadIsPending } from '../shared/thread-utils.js'; import { queueActivityUpdatesActionType } from '../types/activity-types.js'; -import { - useServerCall, - useDispatchActionPromise, -} from '../utils/action-utils.js'; +import { useDispatchActionPromise } from '../utils/action-utils.js'; import { useSelector } from '../utils/redux-utils.js'; type Props = { +activeThread: ?string, +frozen: boolean, }; function ActivityHandler(props: Props): React.Node { const { activeThread, frozen } = props; const prevActiveThreadRef = React.useRef(); React.useEffect(() => { prevActiveThreadRef.current = activeThread; }, [activeThread]); const prevActiveThread = prevActiveThreadRef.current; const connection = useSelector(connectionSelector); invariant(connection, 'keyserver missing from keyserverStore'); const connectionStatus = connection.status; const prevConnectionStatusRef = React.useRef(); React.useEffect(() => { prevConnectionStatusRef.current = connectionStatus; }, [connectionStatus]); const prevConnectionStatus = prevConnectionStatusRef.current; const activeThreadLatestMessage = useSelector(state => { if (!activeThread) { return undefined; } return getMostRecentNonLocalMessageID(activeThread, state.messageStore); }); const prevActiveThreadLatestMessageRef = React.useRef(); React.useEffect(() => { prevActiveThreadLatestMessageRef.current = activeThreadLatestMessage; }, [activeThreadLatestMessage]); const prevActiveThreadLatestMessage = prevActiveThreadLatestMessageRef.current; const canSend = connectionStatus === 'connected' && !frozen; const prevCanSendRef = React.useRef(); React.useEffect(() => { prevCanSendRef.current = canSend; }, [canSend]); const prevCanSend = prevCanSendRef.current; const dispatch = useDispatch(); const dispatchActionPromise = useDispatchActionPromise(); - const callUpdateActivity = useServerCall(updateActivity); + const callUpdateActivity = useUpdateActivity(); React.useEffect(() => { const activityUpdates = []; const isActiveThreadPending = threadIsPending(activeThread); if (activeThread !== prevActiveThread) { const isPrevActiveThreadPending = threadIsPending(prevActiveThread); if (prevActiveThread && !isPrevActiveThreadPending) { activityUpdates.push({ focus: false, threadID: prevActiveThread, latestMessage: prevActiveThreadLatestMessage, }); } if (activeThread && !isActiveThreadPending) { activityUpdates.push({ focus: true, threadID: activeThread, latestMessage: activeThreadLatestMessage, }); } } if ( !frozen && connectionStatus !== 'connected' && prevConnectionStatus === 'connected' && activeThread && !isActiveThreadPending ) { // When the server closes a socket it also deletes any activity rows // associated with that socket's session. If that activity is still // ongoing, we should make sure that we clarify that with the server once // we reconnect. activityUpdates.push({ focus: true, threadID: activeThread, latestMessage: activeThreadLatestMessage, }); } if (activityUpdates.length > 0) { dispatch({ type: queueActivityUpdatesActionType, payload: { activityUpdates }, }); } if (!canSend) { return; } if (!prevCanSend) { activityUpdates.unshift(...connection.queuedActivityUpdates); } if (activityUpdates.length === 0) { return; } dispatchActionPromise( updateActivityActionTypes, - callUpdateActivity(activityUpdates), + callUpdateActivity({ activityUpdates }), ); }); return null; } export default ActivityHandler;