diff --git a/lib/shared/dm-ops/process-dm-ops.js b/lib/shared/dm-ops/process-dm-ops.js index 158f022a1..3184b90f3 100644 --- a/lib/shared/dm-ops/process-dm-ops.js +++ b/lib/shared/dm-ops/process-dm-ops.js @@ -1,419 +1,407 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import type { ProcessDMOperationUtilities } from './dm-op-spec.js'; import { dmOpSpecs } from './dm-op-specs.js'; import { type OutboundDMOperationSpecification, type DMOperationSpecification, useCreateMessagesToPeersFromDMOp, dmOperationSpecificationTypes, type OutboundComposableDMOperationSpecification, useSendDMOperationUtils, } from './dm-op-utils.js'; import { useProcessBlobHolders } from '../../actions/holder-actions.js'; import { processNewUserIDsActionType } from '../../actions/user-actions.js'; import { useDispatchWithMetadata } from '../../hooks/ops-hooks.js'; import { usePeerToPeerCommunication, type ProcessOutboundP2PMessagesResult, } from '../../tunnelbroker/peer-to-peer-context.js'; import { useConfirmPeerToPeerMessage } from '../../tunnelbroker/use-confirm-peer-to-peer-message.js'; import { processDMOpsActionType, queueDMOpsActionType, dmOperationValidator, } from '../../types/dm-ops.js'; -import type { DMOperation } from '../../types/dm-ops.js'; import type { NotificationsCreationData } from '../../types/notif-types.js'; import type { DispatchMetadata } from '../../types/redux-types.js'; import type { OutboundP2PMessage } from '../../types/sqlite-types.js'; -import { getConfig } from '../../utils/config.js'; import { extractUserIDsFromPayload } from '../../utils/conversion-utils.js'; -import { isDev } from '../../utils/dev-utils.js'; import { useSelector, useDispatch } from '../../utils/redux-utils.js'; -import { useIsCurrentUserStaff } from '../staff-utils.js'; +import { useStaffAlert } from '../staff-utils.js'; function useProcessDMOperation(): ( dmOperationSpecification: DMOperationSpecification, dmOpID: ?string, ) => Promise { const baseUtilities = useSendDMOperationUtils(); const dispatchWithMetadata = useDispatchWithMetadata(); const processBlobHolders = useProcessBlobHolders(); const createMessagesToPeersFromDMOp = useCreateMessagesToPeersFromDMOp(); const confirmPeerToPeerMessage = useConfirmPeerToPeerMessage(); const dispatch = useDispatch(); - const isCurrentUserStaff = useIsCurrentUserStaff(); - const showOperationAlertToStaff = React.useCallback( - (description: string, operation: DMOperation) => { - if (!isCurrentUserStaff && !isDev) { - return; - } - getConfig().showAlert(description, JSON.stringify(operation)); - }, - [isCurrentUserStaff], - ); + const { showAlertToStaff } = useStaffAlert(); return React.useCallback( async ( dmOperationSpecification: DMOperationSpecification, dmOpID: ?string, ) => { const { viewerID, ...restUtilities } = baseUtilities; if (!viewerID) { - showOperationAlertToStaff( + showAlertToStaff( 'Ignored DMOperation because logged out', - dmOperationSpecification.op, + JSON.stringify(dmOperationSpecification.op), ); return; } const utilities: ProcessDMOperationUtilities = { ...restUtilities, viewerID, }; const { op: dmOp } = dmOperationSpecification; let outboundP2PMessages: ?$ReadOnlyArray = null; if ( dmOperationSpecification.type === dmOperationSpecificationTypes.OUTBOUND ) { outboundP2PMessages = await createMessagesToPeersFromDMOp( dmOp, dmOperationSpecification.recipients, ); } let dispatchMetadata: ?DispatchMetadata = null; if ( dmOperationSpecification.type === dmOperationSpecificationTypes.OUTBOUND && dmOpID ) { dispatchMetadata = { dmOpID, }; } else if ( dmOperationSpecification.type === dmOperationSpecificationTypes.INBOUND ) { dispatchMetadata = dmOperationSpecification.metadata; } let composableMessageID: ?string = null; if ( dmOperationSpecification.type === dmOperationSpecificationTypes.OUTBOUND && !dmOpSpecs[dmOp.type].supportsAutoRetry ) { composableMessageID = dmOp.messageID; } if ( dmOperationSpecification.type === dmOperationSpecificationTypes.OUTBOUND && dmOperationSpecification.sendOnly ) { const notificationsCreationData = await dmOpSpecs[ dmOp.type ].notificationsCreationData?.(dmOp, utilities); dispatchWithMetadata( { type: processDMOpsActionType, payload: { rawMessageInfos: [], updateInfos: [], outboundP2PMessages, composableMessageID, notificationsCreationData, }, }, dispatchMetadata, ); return; } if (!dmOpSpecs[dmOp.type].operationValidator.is(dmOp)) { - showOperationAlertToStaff( + showAlertToStaff( "Ignoring operation because it doesn't pass validation", - dmOp, + JSON.stringify(dmOp), ); await confirmPeerToPeerMessage(dispatchMetadata); return; } const processingCheckResult = await dmOpSpecs[dmOp.type].canBeProcessed( dmOp, utilities, ); if (!processingCheckResult.isProcessingPossible) { if (processingCheckResult.reason.type === 'invalid') { - showOperationAlertToStaff( + showAlertToStaff( 'Ignoring operation because it is invalid', - dmOp, + JSON.stringify(dmOp), ); await confirmPeerToPeerMessage(dispatchMetadata); return; } let condition; if (processingCheckResult.reason.type === 'missing_thread') { condition = { type: 'thread', threadID: processingCheckResult.reason.threadID, }; } else if (processingCheckResult.reason.type === 'missing_entry') { condition = { type: 'entry', entryID: processingCheckResult.reason.entryID, }; } else if (processingCheckResult.reason.type === 'missing_message') { condition = { type: 'message', messageID: processingCheckResult.reason.messageID, }; } else if (processingCheckResult.reason.type === 'missing_membership') { condition = { type: 'membership', threadID: processingCheckResult.reason.threadID, userID: processingCheckResult.reason.userID, }; } if (condition?.type) { - showOperationAlertToStaff( + showAlertToStaff( `Adding operation to the ${condition.type} queue`, - dmOp, + JSON.stringify(dmOp), ); } else { - showOperationAlertToStaff( + showAlertToStaff( 'Operation should be added to a queue but its type is missing', - dmOp, + JSON.stringify(dmOp), ); } dispatchWithMetadata( { type: queueDMOpsActionType, payload: { operation: dmOp, timestamp: Date.now(), condition, }, }, dispatchMetadata, ); await confirmPeerToPeerMessage(dispatchMetadata); return; } const newUserIDs = extractUserIDsFromPayload(dmOperationValidator, dmOp); if (newUserIDs.length > 0) { dispatch({ type: processNewUserIDsActionType, payload: { userIDs: newUserIDs }, }); } const dmOpSpec = dmOpSpecs[dmOp.type]; const notificationsCreationDataPromise: Promise = (async () => { if ( dmOperationSpecification.type === dmOperationSpecificationTypes.INBOUND || !dmOpSpec.notificationsCreationData ) { return null; } return await dmOpSpec.notificationsCreationData(dmOp, utilities); })(); const [ { rawMessageInfos, updateInfos, blobOps }, notificationsCreationData, ] = await Promise.all([ dmOpSpec.processDMOperation(dmOp, utilities), notificationsCreationDataPromise, ]); const holderOps = blobOps .map(({ dmOpType, ...holderOp }) => { if ( (dmOpType === 'inbound_only' && dmOperationSpecification.type === dmOperationSpecificationTypes.OUTBOUND) || (dmOpType === 'outbound_only' && dmOperationSpecification.type === dmOperationSpecificationTypes.INBOUND) ) { return null; } return holderOp; }) .filter(Boolean); void processBlobHolders(holderOps); dispatchWithMetadata( { type: processDMOpsActionType, payload: { rawMessageInfos, updateInfos, outboundP2PMessages, composableMessageID, notificationsCreationData, }, }, dispatchMetadata, ); }, [ baseUtilities, processBlobHolders, dispatchWithMetadata, - showOperationAlertToStaff, + showAlertToStaff, createMessagesToPeersFromDMOp, confirmPeerToPeerMessage, dispatch, ], ); } function useProcessAndSendDMOperation(): ( dmOperationSpecification: OutboundDMOperationSpecification, ) => Promise { const processDMOps = useProcessDMOperation(); const { getDMOpsSendingPromise } = usePeerToPeerCommunication(); return React.useCallback( async (dmOperationSpecification: OutboundDMOperationSpecification) => { const { promise, dmOpID } = getDMOpsSendingPromise(); await processDMOps(dmOperationSpecification, dmOpID); await promise; }, [getDMOpsSendingPromise, processDMOps], ); } function useSendComposableDMOperation(): ( dmOperationSpecification: OutboundComposableDMOperationSpecification, ) => Promise { const { getDMOpsSendingPromise } = usePeerToPeerCommunication(); const dispatchWithMetadata = useDispatchWithMetadata(); const baseUtilities = useSendDMOperationUtils(); const { processOutboundMessages } = usePeerToPeerCommunication(); const localMessageInfos = useSelector(state => state.messageStore.local); const createMessagesToPeersFromDMOp = useCreateMessagesToPeersFromDMOp(); return React.useCallback( async ( dmOperationSpecification: OutboundComposableDMOperationSpecification, ): Promise => { const { viewerID, ...restUtilities } = baseUtilities; if (!viewerID) { console.log('ignored DMOperation because logged out'); return { result: 'failure', failedMessageIDs: [], }; } const utilities: ProcessDMOperationUtilities = { ...restUtilities, viewerID, }; const { promise, dmOpID } = getDMOpsSendingPromise(); const { op, composableMessageID, recipients } = dmOperationSpecification; const localMessageInfo = localMessageInfos[composableMessageID]; if ( localMessageInfo?.outboundP2PMessageIDs && localMessageInfo.outboundP2PMessageIDs.length > 0 ) { processOutboundMessages(localMessageInfo.outboundP2PMessageIDs, dmOpID); try { // This code should never throw. return await promise; } catch (e) { invariant( localMessageInfo.outboundP2PMessageIDs, 'outboundP2PMessageIDs should be defined', ); return { result: 'failure', failedMessageIDs: localMessageInfo.outboundP2PMessageIDs, }; } } const outboundP2PMessages = await createMessagesToPeersFromDMOp( op, recipients, ); const spec = dmOpSpecs[op.type]; const notificationsCreationDataPromise: Promise = (async () => { if (!spec?.notificationsCreationData) { return null; } return await spec.notificationsCreationData(op, utilities); })(); const [{ rawMessageInfos, updateInfos }, notificationsCreationData] = await Promise.all([ dmOpSpecs[op.type].processDMOperation(op, utilities), notificationsCreationDataPromise, ]); dispatchWithMetadata( { type: processDMOpsActionType, payload: { rawMessageInfos, updateInfos, outboundP2PMessages, composableMessageID, notificationsCreationData, }, }, { dmOpID, }, ); try { // This code should never throw. return await promise; } catch (e) { return { result: 'failure', failedMessageIDs: outboundP2PMessages.map( message => message.messageID, ), }; } }, [ baseUtilities, getDMOpsSendingPromise, localMessageInfos, createMessagesToPeersFromDMOp, dispatchWithMetadata, processOutboundMessages, ], ); } export { useProcessDMOperation, useProcessAndSendDMOperation, useSendComposableDMOperation, }; diff --git a/lib/shared/staff-utils.js b/lib/shared/staff-utils.js index 827e0cedc..78c07a614 100644 --- a/lib/shared/staff-utils.js +++ b/lib/shared/staff-utils.js @@ -1,27 +1,51 @@ // @flow +import * as React from 'react'; + import bots from '../facts/bots.js'; import staff from '../facts/staff.js'; +import { getConfig } from '../utils/config.js'; +import { isDev } from '../utils/dev-utils.js'; import { useSelector } from '../utils/redux-utils.js'; function isStaff(userID: string): boolean { if (staff.includes(userID)) { return true; } for (const key in bots) { const bot = bots[key]; if (userID === bot.userID) { return true; } } return false; } function useIsCurrentUserStaff(): boolean { const isCurrentUserStaff = useSelector(state => state.currentUserInfo?.id ? isStaff(state.currentUserInfo.id) : false, ); return isCurrentUserStaff; } -export { isStaff, useIsCurrentUserStaff }; +type StaffAlertHook = { + +showAlertToStaff: (title: string, message: string) => void, +}; + +function useStaffAlert(): StaffAlertHook { + const isCurrentUserStaff = useIsCurrentUserStaff(); + + const showAlertToStaff = React.useCallback( + (title: string, message: string) => { + if (!isCurrentUserStaff && !isDev) { + return; + } + getConfig().showAlert(title, message); + }, + [isCurrentUserStaff], + ); + + return { showAlertToStaff }; +} + +export { isStaff, useIsCurrentUserStaff, useStaffAlert };