diff --git a/lib/reducers/thread-reducer.js b/lib/reducers/thread-reducer.js index 401c17044..c68aa8b6e 100644 --- a/lib/reducers/thread-reducer.js +++ b/lib/reducers/thread-reducer.js @@ -1,497 +1,440 @@ // @flow import _isEqual from 'lodash/fp/isEqual.js'; import { setThreadUnreadStatusActionTypes, updateActivityActionTypes, } from '../actions/activity-actions.js'; import { setClientDBStoreActionType } from '../actions/client-db-store-actions.js'; import { saveMessagesActionType } from '../actions/message-actions.js'; import { siweAuthActionTypes } from '../actions/siwe-actions.js'; import { changeThreadSettingsActionTypes, deleteThreadActionTypes, newThreadActionTypes, removeUsersFromThreadActionTypes, changeThreadMemberRolesActionTypes, joinThreadActionTypes, leaveThreadActionTypes, modifyCommunityRoleActionTypes, deleteCommunityRoleActionTypes, } from '../actions/thread-actions.js'; import { logOutActionTypes, deleteAccountActionTypes, logInActionTypes, registerActionTypes, updateSubscriptionActionTypes, } from '../actions/user-actions.js'; import { type ThreadStoreOperation, threadStoreOpsHandlers, } from '../ops/thread-store-ops.js'; +import { updateSpecs } from '../shared/updates/update-specs.js'; import type { BaseAction } from '../types/redux-types.js'; import { type ClientThreadInconsistencyReportCreationRequest, reportTypes, } from '../types/report-types.js'; import { serverRequestTypes, processServerRequestsActionType, } from '../types/request-types.js'; import { fullStateSyncActionType, incrementalStateSyncActionType, } from '../types/socket-types.js'; import type { RawThreadInfo, ThreadStore } from '../types/thread-types.js'; -import { updateTypes } from '../types/update-types-enum.js'; import { type ClientUpdateInfo, processUpdatesActionType, } from '../types/update-types.js'; import { actionLogger } from '../utils/action-logger.js'; import { setNewSessionActionType } from '../utils/action-utils.js'; import { getConfig } from '../utils/config.js'; import { generateReportID } from '../utils/report-utils.js'; import { sanitizeActionSecrets } from '../utils/sanitization.js'; const { processStoreOperations: processThreadStoreOperations } = threadStoreOpsHandlers; function generateOpsForThreadUpdates( threadInfos: { +[id: string]: RawThreadInfo }, payload: { +updatesResult: { +newUpdates: $ReadOnlyArray, ... }, ... }, ): $ReadOnlyArray { - const threadOperations: ThreadStoreOperation[] = []; - for (const update of payload.updatesResult.newUpdates) { - if ( - (update.type === updateTypes.UPDATE_THREAD || - update.type === updateTypes.JOIN_THREAD) && - !_isEqual(threadInfos[update.threadInfo.id])(update.threadInfo) - ) { - threadOperations.push({ - type: 'replace', - payload: { - id: update.threadInfo.id, - threadInfo: update.threadInfo, - }, - }); - } else if ( - update.type === updateTypes.UPDATE_THREAD_READ_STATUS && - threadInfos[update.threadID] && - threadInfos[update.threadID].currentUser.unread !== update.unread - ) { - const updatedThread = { - ...threadInfos[update.threadID], - currentUser: { - ...threadInfos[update.threadID].currentUser, - unread: update.unread, - }, - }; - threadOperations.push({ - type: 'replace', - payload: { - id: update.threadID, - threadInfo: updatedThread, - }, - }); - } else if ( - update.type === updateTypes.DELETE_THREAD && - threadInfos[update.threadID] - ) { - threadOperations.push({ - type: 'remove', - payload: { - ids: [update.threadID], - }, - }); - } else if (update.type === updateTypes.DELETE_ACCOUNT) { - for (const threadID in threadInfos) { - const threadInfo = threadInfos[threadID]; - const newMembers = threadInfo.members.filter( - member => member.id !== update.deletedUserID, - ); - if (newMembers.length < threadInfo.members.length) { - const updatedThread = { - ...threadInfo, - members: newMembers, - }; - threadOperations.push({ - type: 'replace', - payload: { - id: threadID, - threadInfo: updatedThread, - }, - }); - } - } - } - } - return threadOperations; + return payload.updatesResult.newUpdates + .map(update => + updateSpecs[update.type].generateOpsForThreadUpdates?.( + threadInfos, + update, + ), + ) + .filter(Boolean) + .flat(); } function findInconsistencies( action: BaseAction, beforeStateCheck: { +[id: string]: RawThreadInfo }, afterStateCheck: { +[id: string]: RawThreadInfo }, ): ClientThreadInconsistencyReportCreationRequest[] { if (_isEqual(beforeStateCheck)(afterStateCheck)) { return []; } return [ { type: reportTypes.THREAD_INCONSISTENCY, platformDetails: getConfig().platformDetails, beforeAction: beforeStateCheck, action: sanitizeActionSecrets(action), pushResult: afterStateCheck, lastActions: actionLogger.interestingActionSummaries, time: Date.now(), id: generateReportID(), }, ]; } function reduceThreadInfos( state: ThreadStore, action: BaseAction, ): { threadStore: ThreadStore, newThreadInconsistencies: $ReadOnlyArray, threadStoreOperations: $ReadOnlyArray, } { if ( action.type === logInActionTypes.success || action.type === siweAuthActionTypes.success || action.type === registerActionTypes.success || action.type === fullStateSyncActionType ) { const newThreadInfos = action.payload.threadInfos; const threadStoreOperations = [ { type: 'remove_all', }, ...Object.keys(newThreadInfos).map((id: string) => ({ type: 'replace', payload: { id, threadInfo: newThreadInfos[id] }, })), ]; const updatedThreadStore = processThreadStoreOperations( state, threadStoreOperations, ); return { threadStore: updatedThreadStore, newThreadInconsistencies: [], threadStoreOperations, }; } else if ( action.type === logOutActionTypes.success || action.type === deleteAccountActionTypes.success || (action.type === setNewSessionActionType && action.payload.sessionChange.cookieInvalidated) ) { if (Object.keys(state.threadInfos).length === 0) { return { threadStore: state, newThreadInconsistencies: [], threadStoreOperations: [], }; } const threadStoreOperations = [ { type: 'remove_all', }, ]; const updatedThreadStore = processThreadStoreOperations( state, threadStoreOperations, ); return { threadStore: updatedThreadStore, newThreadInconsistencies: [], threadStoreOperations, }; } else if ( action.type === joinThreadActionTypes.success || action.type === leaveThreadActionTypes.success || action.type === deleteThreadActionTypes.success || action.type === changeThreadSettingsActionTypes.success || action.type === removeUsersFromThreadActionTypes.success || action.type === changeThreadMemberRolesActionTypes.success || action.type === incrementalStateSyncActionType || action.type === processUpdatesActionType || action.type === newThreadActionTypes.success || action.type === modifyCommunityRoleActionTypes.success || action.type === deleteCommunityRoleActionTypes.success ) { const { newUpdates } = action.payload.updatesResult; if (newUpdates.length === 0) { return { threadStore: state, newThreadInconsistencies: [], threadStoreOperations: [], }; } const threadStoreOperations = generateOpsForThreadUpdates( state.threadInfos, action.payload, ); const updatedThreadStore = processThreadStoreOperations( state, threadStoreOperations, ); return { threadStore: updatedThreadStore, newThreadInconsistencies: [], threadStoreOperations, }; } else if (action.type === updateSubscriptionActionTypes.success) { const { threadID, subscription } = action.payload; const newThreadInfo = { ...state.threadInfos[threadID], currentUser: { ...state.threadInfos[threadID].currentUser, subscription, }, }; const threadStoreOperations = [ { type: 'replace', payload: { id: threadID, threadInfo: newThreadInfo, }, }, ]; const updatedThreadStore = processThreadStoreOperations( state, threadStoreOperations, ); return { threadStore: updatedThreadStore, newThreadInconsistencies: [], threadStoreOperations, }; } else if (action.type === saveMessagesActionType) { const threadIDToMostRecentTime = new Map(); for (const messageInfo of action.payload.rawMessageInfos) { const current = threadIDToMostRecentTime.get(messageInfo.threadID); if (!current || current < messageInfo.time) { threadIDToMostRecentTime.set(messageInfo.threadID, messageInfo.time); } } const changedThreadInfos = {}; for (const [threadID, mostRecentTime] of threadIDToMostRecentTime) { const threadInfo = state.threadInfos[threadID]; if ( !threadInfo || threadInfo.currentUser.unread || action.payload.updatesCurrentAsOf > mostRecentTime ) { continue; } changedThreadInfos[threadID] = { ...state.threadInfos[threadID], currentUser: { ...state.threadInfos[threadID].currentUser, unread: true, }, }; } if (Object.keys(changedThreadInfos).length !== 0) { const threadStoreOperations = Object.keys(changedThreadInfos).map(id => ({ type: 'replace', payload: { id, threadInfo: changedThreadInfos[id], }, })); const updatedThreadStore = processThreadStoreOperations( state, threadStoreOperations, ); return { threadStore: updatedThreadStore, newThreadInconsistencies: [], threadStoreOperations, }; } } else if (action.type === processServerRequestsActionType) { const checkStateRequest = action.payload.serverRequests.find( candidate => candidate.type === serverRequestTypes.CHECK_STATE, ); if (!checkStateRequest || !checkStateRequest.stateChanges) { return { threadStore: state, newThreadInconsistencies: [], threadStoreOperations: [], }; } const { rawThreadInfos, deleteThreadIDs } = checkStateRequest.stateChanges; if (!rawThreadInfos && !deleteThreadIDs) { return { threadStore: state, newThreadInconsistencies: [], threadStoreOperations: [], }; } const threadStoreOperations: ThreadStoreOperation[] = []; if (rawThreadInfos) { for (const rawThreadInfo of rawThreadInfos) { threadStoreOperations.push({ type: 'replace', payload: { id: rawThreadInfo.id, threadInfo: rawThreadInfo, }, }); } } if (deleteThreadIDs) { threadStoreOperations.push({ type: 'remove', payload: { ids: deleteThreadIDs, }, }); } const updatedThreadStore = processThreadStoreOperations( state, threadStoreOperations, ); const newThreadInconsistencies = findInconsistencies( action, state.threadInfos, updatedThreadStore.threadInfos, ); return { threadStore: updatedThreadStore, newThreadInconsistencies, threadStoreOperations, }; } else if (action.type === updateActivityActionTypes.success) { const updatedThreadInfos = {}; for (const setToUnread of action.payload.result.unfocusedToUnread) { const threadInfo = state.threadInfos[setToUnread]; if (threadInfo && !threadInfo.currentUser.unread) { updatedThreadInfos[setToUnread] = { ...threadInfo, currentUser: { ...threadInfo.currentUser, unread: true, }, }; } } if (Object.keys(updatedThreadInfos).length === 0) { return { threadStore: state, newThreadInconsistencies: [], threadStoreOperations: [], }; } const threadStoreOperations = Object.keys(updatedThreadInfos).map(id => ({ type: 'replace', payload: { id, threadInfo: updatedThreadInfos[id], }, })); const updatedThreadStore = processThreadStoreOperations( state, threadStoreOperations, ); return { threadStore: updatedThreadStore, newThreadInconsistencies: [], threadStoreOperations, }; } else if (action.type === setThreadUnreadStatusActionTypes.started) { const { threadID, unread } = action.payload; const updatedThreadInfo = { ...state.threadInfos[threadID], currentUser: { ...state.threadInfos[threadID].currentUser, unread, }, }; const threadStoreOperations = [ { type: 'replace', payload: { id: threadID, threadInfo: updatedThreadInfo, }, }, ]; const updatedThreadStore = processThreadStoreOperations( state, threadStoreOperations, ); return { threadStore: updatedThreadStore, newThreadInconsistencies: [], threadStoreOperations, }; } else if (action.type === setThreadUnreadStatusActionTypes.success) { const { threadID, resetToUnread } = action.payload; const currentUser = state.threadInfos[threadID].currentUser; if (!resetToUnread || currentUser.unread) { return { threadStore: state, newThreadInconsistencies: [], threadStoreOperations: [], }; } const updatedUser = { ...currentUser, unread: true, }; const updatedThread = { ...state.threadInfos[threadID], currentUser: updatedUser, }; const threadStoreOperations = [ { type: 'replace', payload: { id: threadID, threadInfo: updatedThread, }, }, ]; const updatedThreadStore = processThreadStoreOperations( state, threadStoreOperations, ); return { threadStore: updatedThreadStore, newThreadInconsistencies: [], threadStoreOperations, }; } else if (action.type === setClientDBStoreActionType) { return { threadStore: action.payload.threadStore ?? state, newThreadInconsistencies: [], threadStoreOperations: [], }; } return { threadStore: state, newThreadInconsistencies: [], threadStoreOperations: [], }; } export { reduceThreadInfos }; diff --git a/lib/shared/updates/bad-device-token-spec.js b/lib/shared/updates/bad-device-token-spec.js index 17dd6fd91..e3e989e6f 100644 --- a/lib/shared/updates/bad-device-token-spec.js +++ b/lib/shared/updates/bad-device-token-spec.js @@ -1,5 +1,7 @@ // @flow import type { UpdateSpec } from './update-spec.js'; +import type { BadDeviceTokenUpdateInfo } from '../../types/update-types.js'; -export const badDeviceTokenSpec: UpdateSpec = Object.freeze({}); +export const badDeviceTokenSpec: UpdateSpec = + Object.freeze({}); diff --git a/lib/shared/updates/delete-account-spec.js b/lib/shared/updates/delete-account-spec.js index bc4fe8c8d..ba891898d 100644 --- a/lib/shared/updates/delete-account-spec.js +++ b/lib/shared/updates/delete-account-spec.js @@ -1,5 +1,35 @@ // @flow import type { UpdateSpec } from './update-spec.js'; +import type { RawThreadInfos } from '../../types/thread-types.js'; +import type { AccountDeletionUpdateInfo } from '../../types/update-types.js'; -export const deleteAccountSpec: UpdateSpec = Object.freeze({}); +export const deleteAccountSpec: UpdateSpec = + Object.freeze({ + generateOpsForThreadUpdates( + storeThreadInfos: RawThreadInfos, + update: AccountDeletionUpdateInfo, + ) { + const operations = []; + for (const threadID in storeThreadInfos) { + const threadInfo = storeThreadInfos[threadID]; + const newMembers = threadInfo.members.filter( + member => member.id !== update.deletedUserID, + ); + if (newMembers.length < threadInfo.members.length) { + const updatedThread = { + ...threadInfo, + members: newMembers, + }; + operations.push({ + type: 'replace', + payload: { + id: threadID, + threadInfo: updatedThread, + }, + }); + } + } + return operations; + }, + }); diff --git a/lib/shared/updates/delete-thread-spec.js b/lib/shared/updates/delete-thread-spec.js index 5cdcaaa2b..dd1ddacec 100644 --- a/lib/shared/updates/delete-thread-spec.js +++ b/lib/shared/updates/delete-thread-spec.js @@ -1,5 +1,25 @@ // @flow import type { UpdateSpec } from './update-spec.js'; +import type { RawThreadInfos } from '../../types/thread-types.js'; +import type { ThreadDeletionUpdateInfo } from '../../types/update-types.js'; -export const deleteThreadSpec: UpdateSpec = Object.freeze({}); +export const deleteThreadSpec: UpdateSpec = + Object.freeze({ + generateOpsForThreadUpdates( + storeThreadInfos: RawThreadInfos, + update: ThreadDeletionUpdateInfo, + ) { + if (storeThreadInfos[update.threadID]) { + return [ + { + type: 'remove', + payload: { + ids: [update.threadID], + }, + }, + ]; + } + return null; + }, + }); diff --git a/lib/shared/updates/join-thread-spec.js b/lib/shared/updates/join-thread-spec.js index 2cfe49cb1..692412f71 100644 --- a/lib/shared/updates/join-thread-spec.js +++ b/lib/shared/updates/join-thread-spec.js @@ -1,5 +1,27 @@ // @flow +import _isEqual from 'lodash/fp/isEqual.js'; + import type { UpdateSpec } from './update-spec.js'; +import type { RawThreadInfos } from '../../types/thread-types.js'; +import type { ThreadJoinUpdateInfo } from '../../types/update-types.js'; -export const joinThreadSpec: UpdateSpec = Object.freeze({}); +export const joinThreadSpec: UpdateSpec = Object.freeze({ + generateOpsForThreadUpdates( + storeThreadInfos: RawThreadInfos, + update: ThreadJoinUpdateInfo, + ) { + if (_isEqual(storeThreadInfos[update.threadInfo.id])(update.threadInfo)) { + return null; + } + return [ + { + type: 'replace', + payload: { + id: update.threadInfo.id, + threadInfo: update.threadInfo, + }, + }, + ]; + }, +}); diff --git a/lib/shared/updates/update-current-user-spec.js b/lib/shared/updates/update-current-user-spec.js index e894ed8a9..b11baf8e4 100644 --- a/lib/shared/updates/update-current-user-spec.js +++ b/lib/shared/updates/update-current-user-spec.js @@ -1,5 +1,7 @@ // @flow import type { UpdateSpec } from './update-spec.js'; +import type { CurrentUserUpdateInfo } from '../../types/update-types.js'; -export const updateCurrentUserSpec: UpdateSpec = Object.freeze({}); +export const updateCurrentUserSpec: UpdateSpec = + Object.freeze({}); diff --git a/lib/shared/updates/update-entry-spec.js b/lib/shared/updates/update-entry-spec.js index 294e58c6a..c19ef485b 100644 --- a/lib/shared/updates/update-entry-spec.js +++ b/lib/shared/updates/update-entry-spec.js @@ -1,5 +1,6 @@ // @flow import type { UpdateSpec } from './update-spec.js'; +import type { EntryUpdateInfo } from '../../types/update-types.js'; -export const updateEntrySpec: UpdateSpec = Object.freeze({}); +export const updateEntrySpec: UpdateSpec = Object.freeze({}); diff --git a/lib/shared/updates/update-spec.js b/lib/shared/updates/update-spec.js index c904cd308..0ad06f093 100644 --- a/lib/shared/updates/update-spec.js +++ b/lib/shared/updates/update-spec.js @@ -1,3 +1,12 @@ // @flow -export type UpdateSpec = {}; +import type { ThreadStoreOperation } from '../../ops/thread-store-ops.js'; +import type { RawThreadInfos } from '../../types/thread-types.js'; +import type { ClientUpdateInfo } from '../../types/update-types.js'; + +export type UpdateSpec = { + +generateOpsForThreadUpdates?: ( + storeThreadInfos: RawThreadInfos, + update: UpdateInfo, + ) => ?$ReadOnlyArray, +}; diff --git a/lib/shared/updates/update-specs.js b/lib/shared/updates/update-specs.js index d24cc9a0d..f23a168eb 100644 --- a/lib/shared/updates/update-specs.js +++ b/lib/shared/updates/update-specs.js @@ -1,27 +1,27 @@ // @flow import { badDeviceTokenSpec } from './bad-device-token-spec.js'; import { deleteAccountSpec } from './delete-account-spec.js'; import { deleteThreadSpec } from './delete-thread-spec.js'; import { joinThreadSpec } from './join-thread-spec.js'; import { updateCurrentUserSpec } from './update-current-user-spec.js'; import { updateEntrySpec } from './update-entry-spec.js'; import type { UpdateSpec } from './update-spec.js'; import { updateThreadReadStatusSpec } from './update-thread-read-status-spec.js'; import { updateThreadSpec } from './update-thread-spec.js'; import { updateUserSpec } from './update-user-spec.js'; import { updateTypes, type UpdateType } from '../../types/update-types-enum.js'; export const updateSpecs: { - +[UpdateType]: UpdateSpec, + +[UpdateType]: UpdateSpec<*>, } = Object.freeze({ [updateTypes.DELETE_ACCOUNT]: deleteAccountSpec, [updateTypes.UPDATE_THREAD]: updateThreadSpec, [updateTypes.UPDATE_THREAD_READ_STATUS]: updateThreadReadStatusSpec, [updateTypes.DELETE_THREAD]: deleteThreadSpec, [updateTypes.JOIN_THREAD]: joinThreadSpec, [updateTypes.BAD_DEVICE_TOKEN]: badDeviceTokenSpec, [updateTypes.UPDATE_ENTRY]: updateEntrySpec, [updateTypes.UPDATE_CURRENT_USER]: updateCurrentUserSpec, [updateTypes.UPDATE_USER]: updateUserSpec, }); diff --git a/lib/shared/updates/update-thread-read-status-spec.js b/lib/shared/updates/update-thread-read-status-spec.js index 80772ae1b..4ec47b6d5 100644 --- a/lib/shared/updates/update-thread-read-status-spec.js +++ b/lib/shared/updates/update-thread-read-status-spec.js @@ -1,5 +1,40 @@ // @flow import type { UpdateSpec } from './update-spec.js'; +import type { + RawThreadInfo, + RawThreadInfos, +} from '../../types/thread-types.js'; +import type { ThreadReadStatusUpdateInfo } from '../../types/update-types.js'; -export const updateThreadReadStatusSpec: UpdateSpec = Object.freeze({}); +export const updateThreadReadStatusSpec: UpdateSpec = + Object.freeze({ + generateOpsForThreadUpdates( + storeThreadInfos: RawThreadInfos, + update: ThreadReadStatusUpdateInfo, + ) { + const storeThreadInfo: ?RawThreadInfo = storeThreadInfos[update.threadID]; + if ( + !storeThreadInfo || + storeThreadInfo.currentUser.unread === update.unread + ) { + return null; + } + const updatedThread = { + ...storeThreadInfo, + currentUser: { + ...storeThreadInfo.currentUser, + unread: update.unread, + }, + }; + return [ + { + type: 'replace', + payload: { + id: update.threadID, + threadInfo: updatedThread, + }, + }, + ]; + }, + }); diff --git a/lib/shared/updates/update-thread-spec.js b/lib/shared/updates/update-thread-spec.js index ef4541ef7..34d8d9569 100644 --- a/lib/shared/updates/update-thread-spec.js +++ b/lib/shared/updates/update-thread-spec.js @@ -1,5 +1,27 @@ // @flow +import _isEqual from 'lodash/fp/isEqual.js'; + import type { UpdateSpec } from './update-spec.js'; +import type { RawThreadInfos } from '../../types/thread-types.js'; +import type { ThreadUpdateInfo } from '../../types/update-types.js'; -export const updateThreadSpec: UpdateSpec = Object.freeze({}); +export const updateThreadSpec: UpdateSpec = Object.freeze({ + generateOpsForThreadUpdates( + storeThreadInfos: RawThreadInfos, + update: ThreadUpdateInfo, + ) { + if (_isEqual(storeThreadInfos[update.threadInfo.id])(update.threadInfo)) { + return null; + } + return [ + { + type: 'replace', + payload: { + id: update.threadInfo.id, + threadInfo: update.threadInfo, + }, + }, + ]; + }, +}); diff --git a/lib/shared/updates/update-user-spec.js b/lib/shared/updates/update-user-spec.js index 880713130..58dc63b29 100644 --- a/lib/shared/updates/update-user-spec.js +++ b/lib/shared/updates/update-user-spec.js @@ -1,5 +1,6 @@ // @flow import type { UpdateSpec } from './update-spec.js'; +import type { UserUpdateInfo } from '../../types/update-types.js'; -export const updateUserSpec: UpdateSpec = Object.freeze({}); +export const updateUserSpec: UpdateSpec = Object.freeze({}); diff --git a/lib/types/update-types.js b/lib/types/update-types.js index d1a78ad8a..34bdc160b 100644 --- a/lib/types/update-types.js +++ b/lib/types/update-types.js @@ -1,399 +1,399 @@ // @flow import t, { type TUnion, type TInterface } from 'tcomb'; import { type RawEntryInfo, rawEntryInfoValidator } from './entry-types.js'; import { type RawMessageInfo, rawMessageInfoValidator, type MessageTruncationStatus, messageTruncationStatusValidator, } from './message-types.js'; import { type RawThreadInfo, rawThreadInfoValidator } from './thread-types.js'; import { updateTypes } from './update-types-enum.js'; import { type UserInfo, userInfoValidator, type UserInfos, userInfosValidator, type LoggedInUserInfo, loggedInUserInfoValidator, } from './user-types.js'; import { tNumber, tShape, tID } from '../utils/validation-utils.js'; type AccountDeletionData = { +deletedUserID: string, }; type ThreadData = { +threadID: string, }; type ThreadReadStatusData = { +threadID: string, +unread: boolean, }; type ThreadDeletionData = { +threadID: string, }; type ThreadJoinData = { +threadID: string, }; type BadDeviceTokenData = { +deviceToken: string, }; type EntryData = { +entryID: string, }; type CurrentUserData = {}; type UserData = { // ID of the UserInfo being updated +updatedUserID: string, }; type SharedUpdateData = { +userID: string, +time: number, }; type AccountDeletionUpdateData = { ...SharedUpdateData, ...AccountDeletionData, +type: 0, }; type ThreadUpdateData = { ...SharedUpdateData, ...ThreadData, +type: 1, +targetSession?: string, }; type ThreadReadStatusUpdateData = { ...SharedUpdateData, ...ThreadReadStatusData, +type: 2, }; type ThreadDeletionUpdateData = { ...SharedUpdateData, ...ThreadDeletionData, +type: 3, }; type ThreadJoinUpdateData = { ...SharedUpdateData, ...ThreadJoinData, +type: 4, +targetSession?: string, }; type BadDeviceTokenUpdateData = { ...SharedUpdateData, ...BadDeviceTokenData, +type: 5, +targetCookie: string, }; type EntryUpdateData = { ...SharedUpdateData, ...EntryData, +type: 6, +targetSession: string, }; type CurrentUserUpdateData = { ...SharedUpdateData, ...CurrentUserData, +type: 7, }; type UserUpdateData = { ...SharedUpdateData, ...UserData, +type: 8, +targetSession?: string, }; export type UpdateData = | AccountDeletionUpdateData | ThreadUpdateData | ThreadReadStatusUpdateData | ThreadDeletionUpdateData | ThreadJoinUpdateData | BadDeviceTokenUpdateData | EntryUpdateData | CurrentUserUpdateData | UserUpdateData; type SharedRawUpdateInfo = { +id: string, +time: number, }; type AccountDeletionRawUpdateInfo = { ...SharedRawUpdateInfo, ...AccountDeletionData, +type: 0, }; type ThreadRawUpdateInfo = { ...SharedRawUpdateInfo, ...ThreadData, +type: 1, }; type ThreadReadStatusRawUpdateInfo = { ...SharedRawUpdateInfo, ...ThreadReadStatusData, +type: 2, }; type ThreadDeletionRawUpdateInfo = { ...SharedRawUpdateInfo, ...ThreadDeletionData, +type: 3, }; type ThreadJoinRawUpdateInfo = { ...SharedRawUpdateInfo, ...ThreadJoinData, +type: 4, }; type BadDeviceTokenRawUpdateInfo = { ...SharedRawUpdateInfo, ...BadDeviceTokenData, +type: 5, }; type EntryRawUpdateInfo = { ...SharedRawUpdateInfo, ...EntryData, +type: 6, }; type CurrentUserRawUpdateInfo = { ...SharedRawUpdateInfo, ...CurrentUserData, +type: 7, }; type UserRawUpdateInfo = { ...SharedRawUpdateInfo, ...UserData, +type: 8, }; export type RawUpdateInfo = | AccountDeletionRawUpdateInfo | ThreadRawUpdateInfo | ThreadReadStatusRawUpdateInfo | ThreadDeletionRawUpdateInfo | ThreadJoinRawUpdateInfo | BadDeviceTokenRawUpdateInfo | EntryRawUpdateInfo | CurrentUserRawUpdateInfo | UserRawUpdateInfo; -type AccountDeletionUpdateInfo = { +export type AccountDeletionUpdateInfo = { +type: 0, +id: string, +time: number, +deletedUserID: string, }; export const accountDeletionUpdateInfoValidator: TInterface = tShape({ type: tNumber(updateTypes.DELETE_ACCOUNT), id: t.String, time: t.Number, deletedUserID: t.String, }); -type ThreadUpdateInfo = { +export type ThreadUpdateInfo = { +type: 1, +id: string, +time: number, +threadInfo: RawThreadInfo, }; export const threadUpdateInfoValidator: TInterface = tShape({ type: tNumber(updateTypes.UPDATE_THREAD), id: t.String, time: t.Number, threadInfo: rawThreadInfoValidator, }); -type ThreadReadStatusUpdateInfo = { +export type ThreadReadStatusUpdateInfo = { +type: 2, +id: string, +time: number, +threadID: string, +unread: boolean, }; export const threadReadStatusUpdateInfoValidator: TInterface = tShape({ type: tNumber(updateTypes.UPDATE_THREAD_READ_STATUS), id: t.String, time: t.Number, threadID: tID, unread: t.Boolean, }); -type ThreadDeletionUpdateInfo = { +export type ThreadDeletionUpdateInfo = { +type: 3, +id: string, +time: number, +threadID: string, }; export const threadDeletionUpdateInfoValidator: TInterface = tShape({ type: tNumber(updateTypes.DELETE_THREAD), id: t.String, time: t.Number, threadID: tID, }); -type ThreadJoinUpdateInfo = { +export type ThreadJoinUpdateInfo = { +type: 4, +id: string, +time: number, +threadInfo: RawThreadInfo, +rawMessageInfos: $ReadOnlyArray, +truncationStatus: MessageTruncationStatus, +rawEntryInfos: $ReadOnlyArray, }; export const threadJoinUpdateInfoValidator: TInterface = tShape({ type: tNumber(updateTypes.JOIN_THREAD), id: t.String, time: t.Number, threadInfo: rawThreadInfoValidator, rawMessageInfos: t.list(rawMessageInfoValidator), truncationStatus: messageTruncationStatusValidator, rawEntryInfos: t.list(rawEntryInfoValidator), }); -type BadDeviceTokenUpdateInfo = { +export type BadDeviceTokenUpdateInfo = { +type: 5, +id: string, +time: number, +deviceToken: string, }; export const badDeviceTokenUpdateInfoValidator: TInterface = tShape({ type: tNumber(updateTypes.BAD_DEVICE_TOKEN), id: t.String, time: t.Number, deviceToken: t.String, }); -type EntryUpdateInfo = { +export type EntryUpdateInfo = { +type: 6, +id: string, +time: number, +entryInfo: RawEntryInfo, }; export const entryUpdateInfoValidator: TInterface = tShape({ type: tNumber(updateTypes.UPDATE_ENTRY), id: t.String, time: t.Number, entryInfo: rawEntryInfoValidator, }); -type CurrentUserUpdateInfo = { +export type CurrentUserUpdateInfo = { +type: 7, +id: string, +time: number, +currentUserInfo: LoggedInUserInfo, }; -type UserUpdateInfo = { +export type UserUpdateInfo = { +type: 8, +id: string, +time: number, // Updated UserInfo is already contained within the UpdatesResultWithUserInfos +updatedUserID: string, }; export const userUpdateInfoValidator: TInterface = tShape({ type: tNumber(updateTypes.UPDATE_USER), id: t.String, time: t.Number, updatedUserID: t.String, }); export type ClientUpdateInfo = | AccountDeletionUpdateInfo | ThreadUpdateInfo | ThreadReadStatusUpdateInfo | ThreadDeletionUpdateInfo | ThreadJoinUpdateInfo | BadDeviceTokenUpdateInfo | EntryUpdateInfo | CurrentUserUpdateInfo | UserUpdateInfo; type ServerCurrentUserUpdateInfo = { +type: 7, +id: string, +time: number, +currentUserInfo: LoggedInUserInfo, }; export const serverCurrentUserUpdateInfoValidator: TInterface = tShape({ type: tNumber(updateTypes.UPDATE_CURRENT_USER), id: t.String, time: t.Number, currentUserInfo: loggedInUserInfoValidator, }); export type ServerUpdateInfo = | AccountDeletionUpdateInfo | ThreadUpdateInfo | ThreadReadStatusUpdateInfo | ThreadDeletionUpdateInfo | ThreadJoinUpdateInfo | BadDeviceTokenUpdateInfo | EntryUpdateInfo | ServerCurrentUserUpdateInfo | UserUpdateInfo; export const serverUpdateInfoValidator: TUnion = t.union([ accountDeletionUpdateInfoValidator, threadUpdateInfoValidator, threadReadStatusUpdateInfoValidator, threadDeletionUpdateInfoValidator, threadJoinUpdateInfoValidator, badDeviceTokenUpdateInfoValidator, entryUpdateInfoValidator, serverCurrentUserUpdateInfoValidator, userUpdateInfoValidator, ]); export type ServerUpdatesResult = { +currentAsOf: number, +newUpdates: $ReadOnlyArray, }; export const serverUpdatesResultValidator: TInterface = tShape({ currentAsOf: t.Number, newUpdates: t.list(serverUpdateInfoValidator), }); export type ServerUpdatesResultWithUserInfos = { +updatesResult: ServerUpdatesResult, +userInfos: $ReadOnlyArray, }; export const serverUpdatesResultWithUserInfosValidator: TInterface = tShape({ updatesResult: serverUpdatesResultValidator, userInfos: t.list(userInfoValidator), }); export type ClientUpdatesResult = { +currentAsOf: number, +newUpdates: $ReadOnlyArray, }; export type ClientUpdatesResultWithUserInfos = { +updatesResult: ClientUpdatesResult, +userInfos: $ReadOnlyArray, }; export type CreateUpdatesResult = { +viewerUpdates: $ReadOnlyArray, +userInfos: UserInfos, }; export const createUpdatesResultValidator: TInterface = tShape({ viewerUpdates: t.list(serverUpdateInfoValidator), userInfos: userInfosValidator, }); export type ServerCreateUpdatesResponse = { +viewerUpdates: $ReadOnlyArray, +userInfos: $ReadOnlyArray, }; export const serverCreateUpdatesResponseValidator: TInterface = tShape({ viewerUpdates: t.list(serverUpdateInfoValidator), userInfos: t.list(userInfoValidator), }); export type ClientCreateUpdatesResponse = { +viewerUpdates: $ReadOnlyArray, +userInfos: $ReadOnlyArray, }; export const processUpdatesActionType = 'PROCESS_UPDATES';