diff --git a/lib/ops/thread-store-ops.js b/lib/ops/thread-store-ops.js index 26dbbc383..9daab3960 100644 --- a/lib/ops/thread-store-ops.js +++ b/lib/ops/thread-store-ops.js @@ -1,99 +1,99 @@ // @flow import { type BaseStoreOpsHandlers } from './base-ops.js'; +import type { MinimallyEncodedRawThreadInfo } from '../types/minimally-encoded-thread-permissions-types.js'; import type { ClientDBThreadInfo, - RawThreadInfo, - RawThreadInfos, + MinimallyEncodedRawThreadInfos, ThreadStore, } from '../types/thread-types.js'; import { convertClientDBThreadInfoToRawThreadInfo, convertRawThreadInfoToClientDBThreadInfo, } from '../utils/thread-ops-utils.js'; export type RemoveThreadOperation = { +type: 'remove', +payload: { +ids: $ReadOnlyArray }, }; export type RemoveAllThreadsOperation = { +type: 'remove_all', }; export type ReplaceThreadOperation = { +type: 'replace', - +payload: { +id: string, +threadInfo: RawThreadInfo }, + +payload: { +id: string, +threadInfo: MinimallyEncodedRawThreadInfo }, }; export type ThreadStoreOperation = | RemoveThreadOperation | RemoveAllThreadsOperation | ReplaceThreadOperation; export type ClientDBReplaceThreadOperation = { +type: 'replace', +payload: ClientDBThreadInfo, }; export type ClientDBThreadStoreOperation = | RemoveThreadOperation | RemoveAllThreadsOperation | ClientDBReplaceThreadOperation; export const threadStoreOpsHandlers: BaseStoreOpsHandlers< ThreadStore, ThreadStoreOperation, ClientDBThreadStoreOperation, - RawThreadInfos, + MinimallyEncodedRawThreadInfos, ClientDBThreadInfo, > = { processStoreOperations( store: ThreadStore, ops: $ReadOnlyArray, ): ThreadStore { if (ops.length === 0) { return store; } let processedThreads = { ...store.threadInfos }; for (const operation of ops) { if (operation.type === 'replace') { processedThreads[operation.payload.id] = operation.payload.threadInfo; } else if (operation.type === 'remove') { for (const id of operation.payload.ids) { delete processedThreads[id]; } } else if (operation.type === 'remove_all') { processedThreads = {}; } } return { ...store, threadInfos: processedThreads }; }, convertOpsToClientDBOps( ops: $ReadOnlyArray, ): $ReadOnlyArray { return ops.map(threadStoreOperation => { if (threadStoreOperation.type === 'replace') { return { type: 'replace', payload: convertRawThreadInfoToClientDBThreadInfo( threadStoreOperation.payload.threadInfo, ), }; } return threadStoreOperation; }); }, translateClientDBData(data: $ReadOnlyArray): { - +[id: string]: RawThreadInfo, + +[id: string]: MinimallyEncodedRawThreadInfo, } { return Object.fromEntries( data.map((dbThreadInfo: ClientDBThreadInfo) => [ dbThreadInfo.id, convertClientDBThreadInfoToRawThreadInfo(dbThreadInfo), ]), ); }, }; diff --git a/lib/reducers/thread-reducer.js b/lib/reducers/thread-reducer.js index 7b69b74ec..d7a4e5626 100644 --- a/lib/reducers/thread-reducer.js +++ b/lib/reducers/thread-reducer.js @@ -1,524 +1,426 @@ // @flow +import invariant from 'invariant'; + 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, deleteKeyserverAccountActionTypes, logInActionTypes, keyserverRegisterActionTypes, updateSubscriptionActionTypes, } from '../actions/user-actions.js'; import { setNewSessionActionType } from '../keyserver-conn/keyserver-conn-types.js'; import { type ThreadStoreOperation, threadStoreOpsHandlers, } from '../ops/thread-store-ops.js'; import { stateSyncSpecs } from '../shared/state-sync/state-sync-specs.js'; import { updateSpecs } from '../shared/updates/update-specs.js'; +import type { MinimallyEncodedRawThreadInfo } from '../types/minimally-encoded-thread-permissions-types.js'; import type { BaseAction } from '../types/redux-types.js'; import { type ClientThreadInconsistencyReportCreationRequest } from '../types/report-types.js'; import { serverRequestTypes, processServerRequestsActionType, } from '../types/request-types.js'; import { fullStateSyncActionType, incrementalStateSyncActionType, } from '../types/socket-types.js'; import type { - RawThreadInfo, - RawThreadInfos, + MinimallyEncodedRawThreadInfos, ThreadStore, } from '../types/thread-types.js'; import { type ClientUpdateInfo, processUpdatesActionType, } from '../types/update-types.js'; const { processStoreOperations: processThreadStoreOperations } = threadStoreOpsHandlers; function generateOpsForThreadUpdates( - threadInfos: RawThreadInfos, + threadInfos: MinimallyEncodedRawThreadInfos, payload: { +updatesResult: { +newUpdates: $ReadOnlyArray, ... }, ... }, ): $ReadOnlyArray { return payload.updatesResult.newUpdates .map(update => updateSpecs[update.type].generateOpsForThreadUpdates?.( threadInfos, update, ), ) .filter(Boolean) .flat(); } function reduceThreadInfos( state: ThreadStore, action: BaseAction, ): { threadStore: ThreadStore, newThreadInconsistencies: $ReadOnlyArray, threadStoreOperations: $ReadOnlyArray, } { if ( action.type === logInActionTypes.success || action.type === siweAuthActionTypes.success || action.type === keyserverRegisterActionTypes.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] }, - })), + ...Object.keys(newThreadInfos).map((id: string) => { + invariant( + newThreadInfos[id].minimallyEncoded, + 'newThreadInfos must be minimallyEncoded for current clients', + ); + return { + 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 === deleteKeyserverAccountActionTypes.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 threadInfo = state.threadInfos[threadID]; - // TODO (atul): Try to get rid of this ridiculous branching. - if (threadInfo.minimallyEncoded) { - const newThreadInfo = { - ...threadInfo, - currentUser: { - ...threadInfo.currentUser, - subscription, - }, - }; - const threadStoreOperations = [ - { - type: 'replace', - payload: { - id: threadID, - threadInfo: newThreadInfo, - }, - }, - ]; - const updatedThreadStore = processThreadStoreOperations( - state, - threadStoreOperations, - ); - return { - threadStore: updatedThreadStore, - newThreadInconsistencies: [], - threadStoreOperations, - }; - } else { - const newThreadInfo = { - ...threadInfo, - currentUser: { - ...threadInfo.currentUser, - subscription, - }, - }; - const threadStoreOperations = [ - { - type: 'replace', - payload: { - id: threadID, - threadInfo: newThreadInfo, - }, + const newThreadInfo = { + ...threadInfo, + currentUser: { + ...threadInfo.currentUser, + subscription, + }, + }; + const threadStoreOperations = [ + { + type: 'replace', + payload: { + id: threadID, + threadInfo: newThreadInfo, }, - ]; - const updatedThreadStore = processThreadStoreOperations( - state, - threadStoreOperations, - ); - return { - threadStore: updatedThreadStore, - newThreadInconsistencies: [], - threadStoreOperations, - }; - } + }, + ]; + 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: { [string]: RawThreadInfo } = {}; + const changedThreadInfos: { [string]: MinimallyEncodedRawThreadInfo } = {}; for (const [threadID, mostRecentTime] of threadIDToMostRecentTime) { const threadInfo = state.threadInfos[threadID]; if ( !threadInfo || threadInfo.currentUser.unread || action.payload.updatesCurrentAsOf > mostRecentTime ) { continue; } - const changedThreadInfo = state.threadInfos[threadID]; - // TODO (atul): Try to get rid of this ridiculous branching. - if (changedThreadInfo.minimallyEncoded) { - changedThreadInfos[threadID] = { - ...changedThreadInfo, - currentUser: { - ...changedThreadInfo.currentUser, - unread: true, - }, - }; - } else { - changedThreadInfos[threadID] = { - ...changedThreadInfo, - currentUser: { - ...changedThreadInfo.currentUser, - unread: true, - }, - }; - } + changedThreadInfos[threadID] = { + ...threadInfo, + currentUser: { + ...threadInfo.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) { + invariant( + rawThreadInfo.minimallyEncoded, + 'rawThreadInfo must be minimallyEncoded for current clients', + ); 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 = stateSyncSpecs.threads.findStoreInconsistencies( action, state.threadInfos, updatedThreadStore.threadInfos, ); return { threadStore: updatedThreadStore, newThreadInconsistencies, threadStoreOperations, }; } else if (action.type === updateActivityActionTypes.success) { - const updatedThreadInfos: { [string]: RawThreadInfo } = {}; + const updatedThreadInfos: { [string]: MinimallyEncodedRawThreadInfo } = {}; for (const setToUnread of action.payload.result.unfocusedToUnread) { const threadInfo = state.threadInfos[setToUnread]; - // TODO (atul): Try to get rid of this ridiculous branching. - if (threadInfo.minimallyEncoded) { - if (threadInfo && !threadInfo.currentUser.unread) { - updatedThreadInfos[setToUnread] = { - ...threadInfo, - currentUser: { - ...threadInfo.currentUser, - unread: true, - }, - }; - } - } else { - if (threadInfo && !threadInfo.currentUser.unread) { - updatedThreadInfos[setToUnread] = { - ...threadInfo, - currentUser: { - ...threadInfo.currentUser, - unread: true, - }, - }; - } + 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 threadInfo = state.threadInfos[threadID]; - // TODO (atul): Try to get rid of this ridiculous branching. - if (threadInfo.minimallyEncoded) { - const updatedThreadInfo = { - ...threadInfo, - currentUser: { - ...threadInfo.currentUser, - unread, - }, - }; - const threadStoreOperations = [ - { - type: 'replace', - payload: { - id: threadID, - threadInfo: updatedThreadInfo, - }, - }, - ]; - const updatedThreadStore = processThreadStoreOperations( - state, - threadStoreOperations, - ); - return { - threadStore: updatedThreadStore, - newThreadInconsistencies: [], - threadStoreOperations, - }; - } else { - const updatedThreadInfo = { - ...threadInfo, - currentUser: { - ...threadInfo.currentUser, - unread, - }, - }; - const threadStoreOperations = [ - { - type: 'replace', - payload: { - id: threadID, - threadInfo: updatedThreadInfo, - }, + const updatedThreadInfo = { + ...threadInfo, + currentUser: { + ...threadInfo.currentUser, + unread, + }, + }; + const threadStoreOperations = [ + { + type: 'replace', + payload: { + id: threadID, + threadInfo: updatedThreadInfo, }, - ]; - const updatedThreadStore = processThreadStoreOperations( - state, - threadStoreOperations, - ); - return { - threadStore: updatedThreadStore, - newThreadInconsistencies: [], - threadStoreOperations, - }; - } + }, + ]; + 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; + const threadInfo = state.threadInfos[threadID]; + const { currentUser } = threadInfo; if (!resetToUnread || currentUser.unread) { return { threadStore: state, newThreadInconsistencies: [], threadStoreOperations: [], }; } - const threadInfo = state.threadInfos[threadID]; - // TODO (atul): Try to get rid of this ridiculous branching. - if (threadInfo.minimallyEncoded) { - const updatedThread = { - ...threadInfo, - currentUser: { ...threadInfo.currentUser, unread: true }, - }; - const threadStoreOperations = [ - { - type: 'replace', - payload: { - id: threadID, - threadInfo: updatedThread, - }, - }, - ]; - const updatedThreadStore = processThreadStoreOperations( - state, - threadStoreOperations, - ); - return { - threadStore: updatedThreadStore, - newThreadInconsistencies: [], - threadStoreOperations, - }; - } else { - const updatedThread = { - ...threadInfo, - currentUser: { ...threadInfo.currentUser, unread: true }, - }; - const threadStoreOperations = [ - { - type: 'replace', - payload: { - id: threadID, - threadInfo: updatedThread, - }, + const updatedThread = { + ...threadInfo, + currentUser: { ...currentUser, unread: true }, + }; + const threadStoreOperations = [ + { + type: 'replace', + payload: { + id: threadID, + threadInfo: updatedThread, }, - ]; - const updatedThreadStore = processThreadStoreOperations( - state, - threadStoreOperations, - ); - return { - threadStore: updatedThreadStore, - newThreadInconsistencies: [], - threadStoreOperations, - }; - } + }, + ]; + 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/delete-account-spec.js b/lib/shared/updates/delete-account-spec.js index 5f9898614..7c55ba67b 100644 --- a/lib/shared/updates/delete-account-spec.js +++ b/lib/shared/updates/delete-account-spec.js @@ -1,123 +1,103 @@ // @flow import t from 'tcomb'; import type { UpdateSpec } from './update-spec.js'; -import type { RawThreadInfos } from '../../types/thread-types.js'; +import type { MinimallyEncodedRawThreadInfos } from '../../types/thread-types.js'; import { updateTypes } from '../../types/update-types-enum.js'; import type { AccountDeletionRawUpdateInfo, AccountDeletionUpdateData, AccountDeletionUpdateInfo, } from '../../types/update-types.js'; import type { UserInfos } from '../../types/user-types.js'; import { tNumber, tShape } from '../../utils/validation-utils.js'; export const deleteAccountSpec: UpdateSpec< AccountDeletionUpdateInfo, AccountDeletionRawUpdateInfo, AccountDeletionUpdateData, > = Object.freeze({ generateOpsForThreadUpdates( - storeThreadInfos: RawThreadInfos, + storeThreadInfos: MinimallyEncodedRawThreadInfos, update: AccountDeletionUpdateInfo, ) { const operations = []; for (const threadID in storeThreadInfos) { const threadInfo = storeThreadInfos[threadID]; - // TODO (atul): Try to get rid of this ridiculous branching. - if (threadInfo.minimallyEncoded) { - 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, - }, - }); - } - } else { - 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, - }, - }); - } + 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; }, reduceUserInfos(state: UserInfos, update: AccountDeletionUpdateInfo) { const { deletedUserID } = update; if (!state[deletedUserID]) { return state; } const { [deletedUserID]: deleted, ...rest } = state; return rest; }, rawUpdateInfoFromRow(row: Object) { const content = JSON.parse(row.content); return { type: updateTypes.DELETE_ACCOUNT, id: row.id.toString(), time: row.time, deletedUserID: content.deletedUserID, }; }, updateContentForServerDB(data: AccountDeletionUpdateData) { return JSON.stringify({ deletedUserID: data.deletedUserID }); }, rawInfoFromData(data: AccountDeletionUpdateData, id: string) { return { type: updateTypes.DELETE_ACCOUNT, id, time: data.time, deletedUserID: data.deletedUserID, }; }, updateInfoFromRawInfo(info: AccountDeletionRawUpdateInfo) { return { type: updateTypes.DELETE_ACCOUNT, id: info.id, time: info.time, deletedUserID: info.deletedUserID, }; }, deleteCondition: new Set([ updateTypes.DELETE_ACCOUNT, updateTypes.UPDATE_USER, ]), keyForUpdateData(data: AccountDeletionUpdateData) { return data.deletedUserID; }, keyForUpdateInfo(info: AccountDeletionUpdateInfo) { return info.deletedUserID; }, typesOfReplacedUpdatesForMatchingKey: 'all_types', generateOpsForUserInfoUpdates(update: AccountDeletionUpdateInfo) { return [{ type: 'remove_users', payload: { ids: [update.deletedUserID] } }]; }, infoValidator: tShape({ type: tNumber(updateTypes.DELETE_ACCOUNT), id: t.String, time: t.Number, deletedUserID: t.String, }), }); diff --git a/lib/shared/updates/join-thread-spec.js b/lib/shared/updates/join-thread-spec.js index 022bbcfc2..f5110e489 100644 --- a/lib/shared/updates/join-thread-spec.js +++ b/lib/shared/updates/join-thread-spec.js @@ -1,177 +1,181 @@ // @flow import invariant from 'invariant'; import _isEqual from 'lodash/fp/isEqual.js'; import t from 'tcomb'; import type { UpdateInfoFromRawInfoParams, UpdateSpec } from './update-spec.js'; import { rawThreadInfoValidator } from '../../permissions/minimally-encoded-thread-permissions-validators.js'; import { type RawEntryInfo, rawEntryInfoValidator, } from '../../types/entry-types.js'; import type { RawMessageInfo, MessageTruncationStatuses, } from '../../types/message-types.js'; import { messageTruncationStatusValidator, rawMessageInfoValidator, } from '../../types/message-types.js'; -import { type RawThreadInfos } from '../../types/thread-types.js'; +import type { MinimallyEncodedRawThreadInfos } from '../../types/thread-types.js'; import { updateTypes } from '../../types/update-types-enum.js'; import type { ThreadJoinUpdateInfo, ThreadJoinRawUpdateInfo, ThreadJoinUpdateData, } from '../../types/update-types.js'; import { tNumber, tShape } from '../../utils/validation-utils.js'; import { combineTruncationStatuses } from '../message-utils.js'; import { threadInFilterList } from '../thread-utils.js'; export const joinThreadSpec: UpdateSpec< ThreadJoinUpdateInfo, ThreadJoinRawUpdateInfo, ThreadJoinUpdateData, > = Object.freeze({ generateOpsForThreadUpdates( - storeThreadInfos: RawThreadInfos, + storeThreadInfos: MinimallyEncodedRawThreadInfos, update: ThreadJoinUpdateInfo, ) { if (_isEqual(storeThreadInfos[update.threadInfo.id])(update.threadInfo)) { return null; } + invariant( + update.threadInfo.minimallyEncoded, + 'update threadInfo must be minimallyEncoded', + ); return [ { type: 'replace', payload: { id: update.threadInfo.id, threadInfo: update.threadInfo, }, }, ]; }, mergeEntryInfos( entryIDs: Set, mergedEntryInfos: Array, update: ThreadJoinUpdateInfo, ) { for (const entryInfo of update.rawEntryInfos) { const entryID = entryInfo.id; if (!entryID || entryIDs.has(entryID)) { continue; } mergedEntryInfos.push(entryInfo); entryIDs.add(entryID); } }, reduceCalendarThreadFilters( filteredThreadIDs: $ReadOnlySet, update: ThreadJoinUpdateInfo, ) { if ( !threadInFilterList(update.threadInfo) || filteredThreadIDs.has(update.threadInfo.id) ) { return filteredThreadIDs; } return new Set([...filteredThreadIDs, update.threadInfo.id]); }, getRawMessageInfos(update: ThreadJoinUpdateInfo) { return update.rawMessageInfos; }, mergeMessageInfosAndTruncationStatuses( messageIDs: Set, messageInfos: Array, truncationStatuses: MessageTruncationStatuses, update: ThreadJoinUpdateInfo, ) { for (const messageInfo of update.rawMessageInfos) { const messageID = messageInfo.id; if (!messageID || messageIDs.has(messageID)) { continue; } messageInfos.push(messageInfo); messageIDs.add(messageID); } truncationStatuses[update.threadInfo.id] = combineTruncationStatuses( update.truncationStatus, truncationStatuses[update.threadInfo.id], ); }, rawUpdateInfoFromRow(row: Object) { const { threadID } = JSON.parse(row.content); return { type: updateTypes.JOIN_THREAD, id: row.id.toString(), time: row.time, threadID, }; }, updateContentForServerDB(data: ThreadJoinUpdateData) { const { threadID } = data; return JSON.stringify({ threadID }); }, entitiesToFetch(update: ThreadJoinRawUpdateInfo) { return { threadID: update.threadID, detailedThreadID: update.threadID, }; }, rawInfoFromData(data: ThreadJoinUpdateData, id: string) { return { type: updateTypes.JOIN_THREAD, id, time: data.time, threadID: data.threadID, }; }, updateInfoFromRawInfo( info: ThreadJoinRawUpdateInfo, params: UpdateInfoFromRawInfoParams, ) { const { data, rawEntryInfosByThreadID, rawMessageInfosByThreadID } = params; const { threadInfos, calendarResult, messageInfosResult } = data; const threadInfo = threadInfos[info.threadID]; if (!threadInfo) { console.warn( "failed to hydrate updateTypes.JOIN_THREAD because we couldn't " + `fetch RawThreadInfo for ${info.threadID}`, ); return null; } invariant(calendarResult, 'should be set'); const rawEntryInfos = rawEntryInfosByThreadID[info.threadID] ?? []; invariant(messageInfosResult, 'should be set'); const rawMessageInfos = rawMessageInfosByThreadID[info.threadID] ?? []; return { type: updateTypes.JOIN_THREAD, id: info.id, time: info.time, threadInfo, rawMessageInfos, truncationStatus: messageInfosResult.truncationStatuses[info.threadID], rawEntryInfos, }; }, deleteCondition: 'all_types', keyForUpdateData(data: ThreadJoinUpdateData) { return data.threadID; }, keyForUpdateInfo(info: ThreadJoinUpdateInfo) { return info.threadInfo.id; }, typesOfReplacedUpdatesForMatchingKey: 'all_types', infoValidator: tShape({ type: tNumber(updateTypes.JOIN_THREAD), id: t.String, time: t.Number, threadInfo: rawThreadInfoValidator, rawMessageInfos: t.list(rawMessageInfoValidator), truncationStatus: messageTruncationStatusValidator, rawEntryInfos: t.list(rawEntryInfoValidator), }), }); diff --git a/lib/shared/updates/update-spec.js b/lib/shared/updates/update-spec.js index 8118bf8e1..03b522d09 100644 --- a/lib/shared/updates/update-spec.js +++ b/lib/shared/updates/update-spec.js @@ -1,103 +1,106 @@ // @flow import type { TType } from 'tcomb'; import type { ThreadStoreOperation } from '../../ops/thread-store-ops.js'; import type { UserStoreOperation } from '../../ops/user-store-ops.js'; import type { FetchEntryInfosBase, RawEntryInfo, RawEntryInfos, } from '../../types/entry-types.js'; import type { RawMessageInfo, MessageTruncationStatuses, FetchMessageInfosResult, } from '../../types/message-types.js'; -import type { RawThreadInfos } from '../../types/thread-types.js'; +import type { + MinimallyEncodedRawThreadInfos, + RawThreadInfos, +} from '../../types/thread-types.js'; import type { UpdateType } from '../../types/update-types-enum.js'; import type { ClientUpdateInfo, RawUpdateInfo, UpdateData, } from '../../types/update-types.js'; import type { CurrentUserInfo, LoggedInUserInfo, UserInfos, } from '../../types/user-types.js'; export type UpdateInfosRawData = { +threadInfos: RawThreadInfos, +messageInfosResult: ?FetchMessageInfosResult, +calendarResult: ?FetchEntryInfosBase, +entryInfosResult: ?RawEntryInfos, +currentUserInfoResult: ?LoggedInUserInfo, +userInfosResult: ?UserInfos, }; export type UpdateInfoFromRawInfoParams = { +data: UpdateInfosRawData, +rawEntryInfosByThreadID: { +[id: string]: $ReadOnlyArray, }, +rawMessageInfosByThreadID: { +[id: string]: $ReadOnlyArray, }, }; export type UpdateTypes = 'all_types' | $ReadOnlySet; export type UpdateSpec< UpdateInfo: ClientUpdateInfo, RawInfo: RawUpdateInfo, Data: UpdateData, > = { +generateOpsForThreadUpdates?: ( - storeThreadInfos: RawThreadInfos, + storeThreadInfos: MinimallyEncodedRawThreadInfos, update: UpdateInfo, ) => ?$ReadOnlyArray, +mergeEntryInfos?: ( entryIDs: Set, mergedEntryInfos: Array, update: UpdateInfo, ) => void, +reduceCurrentUser?: ( state: ?CurrentUserInfo, update: UpdateInfo, ) => ?CurrentUserInfo, +reduceUserInfos?: (state: UserInfos, update: UpdateInfo) => UserInfos, +reduceCalendarThreadFilters?: ( filteredThreadIDs: $ReadOnlySet, update: UpdateInfo, ) => $ReadOnlySet, +getRawMessageInfos?: (update: UpdateInfo) => $ReadOnlyArray, +mergeMessageInfosAndTruncationStatuses?: ( messageIDs: Set, messageInfos: Array, truncationStatuses: MessageTruncationStatuses, update: UpdateInfo, ) => void, +rawUpdateInfoFromRow: (row: Object) => RawInfo, +updateContentForServerDB: (data: Data) => ?string, +entitiesToFetch?: (update: RawInfo) => { +threadID?: string, +detailedThreadID?: string, +entryID?: string, +currentUser?: boolean, +userID?: string, }, +rawInfoFromData: (data: Data, id: string) => RawInfo, +updateInfoFromRawInfo: ( info: RawInfo, params: UpdateInfoFromRawInfoParams, ) => ?UpdateInfo, +deleteCondition: ?UpdateTypes, +keyForUpdateData?: (data: Data) => string, +keyForUpdateInfo?: (info: UpdateInfo) => string, +typesOfReplacedUpdatesForMatchingKey: ?UpdateTypes, +infoValidator: TType, +generateOpsForUserInfoUpdates?: ( update: UpdateInfo, ) => ?$ReadOnlyArray, }; diff --git a/lib/shared/updates/update-thread-read-status-spec.js b/lib/shared/updates/update-thread-read-status-spec.js index a10f055ee..25658ecb5 100644 --- a/lib/shared/updates/update-thread-read-status-spec.js +++ b/lib/shared/updates/update-thread-read-status-spec.js @@ -1,109 +1,109 @@ // @flow import t from 'tcomb'; import type { UpdateSpec } from './update-spec.js'; -import type { RawThreadInfos } from '../../types/thread-types.js'; +import type { MinimallyEncodedRawThreadInfos } from '../../types/thread-types.js'; import { updateTypes } from '../../types/update-types-enum.js'; import type { ThreadReadStatusUpdateInfo, ThreadReadStatusRawUpdateInfo, ThreadReadStatusUpdateData, } from '../../types/update-types.js'; import { tID, tNumber, tShape } from '../../utils/validation-utils.js'; export const updateThreadReadStatusSpec: UpdateSpec< ThreadReadStatusUpdateInfo, ThreadReadStatusRawUpdateInfo, ThreadReadStatusUpdateData, > = Object.freeze({ generateOpsForThreadUpdates( - storeThreadInfos: RawThreadInfos, + storeThreadInfos: MinimallyEncodedRawThreadInfos, update: ThreadReadStatusUpdateInfo, ) { if ( !storeThreadInfos[update.threadID] || storeThreadInfos[update.threadID].currentUser.unread === update.unread ) { return null; } const storeThreadInfo = storeThreadInfos[update.threadID]; let updatedThread; if (storeThreadInfo.minimallyEncoded) { updatedThread = { ...storeThreadInfo, currentUser: { ...storeThreadInfo.currentUser, unread: update.unread, }, }; } else { updatedThread = { ...storeThreadInfo, currentUser: { ...storeThreadInfo.currentUser, unread: update.unread, }, }; } return [ { type: 'replace', payload: { id: update.threadID, threadInfo: updatedThread, }, }, ]; }, rawUpdateInfoFromRow(row: Object) { const { threadID, unread } = JSON.parse(row.content); return { type: updateTypes.UPDATE_THREAD_READ_STATUS, id: row.id.toString(), time: row.time, threadID, unread, }; }, updateContentForServerDB(data: ThreadReadStatusUpdateData) { const { threadID, unread } = data; return JSON.stringify({ threadID, unread }); }, rawInfoFromData(data: ThreadReadStatusUpdateData, id: string) { return { type: updateTypes.UPDATE_THREAD_READ_STATUS, id, time: data.time, threadID: data.threadID, unread: data.unread, }; }, updateInfoFromRawInfo(info: ThreadReadStatusRawUpdateInfo) { return { type: updateTypes.UPDATE_THREAD_READ_STATUS, id: info.id, time: info.time, threadID: info.threadID, unread: info.unread, }; }, deleteCondition: new Set([updateTypes.UPDATE_THREAD_READ_STATUS]), keyForUpdateData(data: ThreadReadStatusUpdateData) { return data.threadID; }, keyForUpdateInfo(info: ThreadReadStatusUpdateInfo) { return info.threadID; }, typesOfReplacedUpdatesForMatchingKey: new Set([ updateTypes.UPDATE_THREAD_READ_STATUS, ]), infoValidator: tShape({ type: tNumber(updateTypes.UPDATE_THREAD_READ_STATUS), id: t.String, time: t.Number, threadID: tID, unread: t.Boolean, }), }); diff --git a/lib/shared/updates/update-thread-spec.js b/lib/shared/updates/update-thread-spec.js index 422e9cb85..90e8c303c 100644 --- a/lib/shared/updates/update-thread-spec.js +++ b/lib/shared/updates/update-thread-spec.js @@ -1,117 +1,122 @@ // @flow +import invariant from 'invariant'; import _isEqual from 'lodash/fp/isEqual.js'; import t from 'tcomb'; import type { UpdateInfoFromRawInfoParams, UpdateSpec } from './update-spec.js'; import { rawThreadInfoValidator } from '../../permissions/minimally-encoded-thread-permissions-validators.js'; -import { type RawThreadInfos } from '../../types/thread-types.js'; +import type { MinimallyEncodedRawThreadInfos } from '../../types/thread-types.js'; import { updateTypes } from '../../types/update-types-enum.js'; import type { ThreadUpdateInfo, ThreadRawUpdateInfo, ThreadUpdateData, } from '../../types/update-types.js'; import { tNumber, tShape } from '../../utils/validation-utils.js'; import { threadInFilterList } from '../thread-utils.js'; export const updateThreadSpec: UpdateSpec< ThreadUpdateInfo, ThreadRawUpdateInfo, ThreadUpdateData, > = Object.freeze({ generateOpsForThreadUpdates( - storeThreadInfos: RawThreadInfos, + storeThreadInfos: MinimallyEncodedRawThreadInfos, update: ThreadUpdateInfo, ) { if (_isEqual(storeThreadInfos[update.threadInfo.id])(update.threadInfo)) { return null; } + invariant( + update.threadInfo.minimallyEncoded, + 'update threadInfo must be minimallyEncoded', + ); return [ { type: 'replace', payload: { id: update.threadInfo.id, threadInfo: update.threadInfo, }, }, ]; }, reduceCalendarThreadFilters( filteredThreadIDs: $ReadOnlySet, update: ThreadUpdateInfo, ) { if ( threadInFilterList(update.threadInfo) || !filteredThreadIDs.has(update.threadInfo.id) ) { return filteredThreadIDs; } return new Set( [...filteredThreadIDs].filter(id => id !== update.threadInfo.id), ); }, rawUpdateInfoFromRow(row: Object) { const { threadID } = JSON.parse(row.content); return { type: updateTypes.UPDATE_THREAD, id: row.id.toString(), time: row.time, threadID, }; }, updateContentForServerDB(data: ThreadUpdateData) { return JSON.stringify({ threadID: data.threadID }); }, entitiesToFetch(update: ThreadRawUpdateInfo) { return { threadID: update.threadID, }; }, rawInfoFromData(data: ThreadUpdateData, id: string) { return { type: updateTypes.UPDATE_THREAD, id, time: data.time, threadID: data.threadID, }; }, updateInfoFromRawInfo( info: ThreadRawUpdateInfo, params: UpdateInfoFromRawInfoParams, ) { const threadInfo = params.data.threadInfos[info.threadID]; if (!threadInfo) { console.warn( "failed to hydrate updateTypes.UPDATE_THREAD because we couldn't " + `fetch RawThreadInfo for ${info.threadID}`, ); return null; } return { type: updateTypes.UPDATE_THREAD, id: info.id, time: info.time, threadInfo, }; }, deleteCondition: new Set([ updateTypes.UPDATE_THREAD, updateTypes.UPDATE_THREAD_READ_STATUS, ]), keyForUpdateData(data: ThreadUpdateData) { return data.threadID; }, keyForUpdateInfo(info: ThreadUpdateInfo) { return info.threadInfo.id; }, typesOfReplacedUpdatesForMatchingKey: new Set([ updateTypes.UPDATE_THREAD_READ_STATUS, ]), infoValidator: tShape({ type: tNumber(updateTypes.UPDATE_THREAD), id: t.String, time: t.Number, threadInfo: rawThreadInfoValidator, }), }); diff --git a/lib/types/thread-types.js b/lib/types/thread-types.js index eb6c03c73..8095e9599 100644 --- a/lib/types/thread-types.js +++ b/lib/types/thread-types.js @@ -1,506 +1,510 @@ // @flow import t, { type TInterface } from 'tcomb'; import { type AvatarDBContent, type ClientAvatar, clientAvatarValidator, type UpdateUserAvatarRequest, } from './avatar-types.js'; import type { CalendarQuery } from './entry-types.js'; import type { Media } from './media-types.js'; import type { MessageTruncationStatuses, RawMessageInfo, } from './message-types.js'; import type { MinimallyEncodedMemberInfo, MinimallyEncodedRawThreadInfo, MinimallyEncodedRelativeMemberInfo, MinimallyEncodedResolvedThreadInfo, MinimallyEncodedRoleInfo, MinimallyEncodedThreadInfo, } from './minimally-encoded-thread-permissions-types.js'; import { type ThreadSubscription, threadSubscriptionValidator, } from './subscription-types.js'; import { type ThreadPermissionsInfo, threadPermissionsInfoValidator, type ThreadRolePermissionsBlob, threadRolePermissionsBlobValidator, type UserSurfacedPermission, } from './thread-permission-types.js'; import { type ThreadType, threadTypeValidator } from './thread-types-enum.js'; import type { ClientUpdateInfo, ServerUpdateInfo } from './update-types.js'; import type { UserInfo, UserInfos } from './user-types.js'; import { type ThreadEntity, threadEntityValidator, } from '../utils/entity-text.js'; import { tID, tShape } from '../utils/validation-utils.js'; export type LegacyMemberInfo = { +id: string, +role: ?string, +permissions: ThreadPermissionsInfo, +isSender: boolean, }; export const legacyMemberInfoValidator: TInterface = tShape({ id: t.String, role: t.maybe(tID), permissions: threadPermissionsInfoValidator, isSender: t.Boolean, }); export type MemberInfo = LegacyMemberInfo | MinimallyEncodedMemberInfo; export type LegacyRelativeMemberInfo = $ReadOnly<{ ...LegacyMemberInfo, +username: ?string, +isViewer: boolean, }>; const legacyRelativeMemberInfoValidator = tShape({ ...legacyMemberInfoValidator.meta.props, username: t.maybe(t.String), isViewer: t.Boolean, }); export type RelativeMemberInfo = | LegacyRelativeMemberInfo | MinimallyEncodedRelativeMemberInfo; export type LegacyRoleInfo = { +id: string, +name: string, +permissions: ThreadRolePermissionsBlob, +isDefault: boolean, }; export const legacyRoleInfoValidator: TInterface = tShape({ id: tID, name: t.String, permissions: threadRolePermissionsBlobValidator, isDefault: t.Boolean, }); export type RoleInfo = LegacyRoleInfo | MinimallyEncodedRoleInfo; export type ThreadCurrentUserInfo = { +role: ?string, +permissions: ThreadPermissionsInfo, +subscription: ThreadSubscription, +unread: ?boolean, }; export const threadCurrentUserInfoValidator: TInterface = tShape({ role: t.maybe(tID), permissions: threadPermissionsInfoValidator, subscription: threadSubscriptionValidator, unread: t.maybe(t.Boolean), }); export type LegacyRawThreadInfo = { +id: string, +type: ThreadType, +name: ?string, +avatar?: ?ClientAvatar, +description: ?string, +color: string, // hex, without "#" or "0x" +creationTime: number, // millisecond timestamp +parentThreadID: ?string, +containingThreadID: ?string, +community: ?string, +members: $ReadOnlyArray, +roles: { +[id: string]: LegacyRoleInfo }, +currentUser: ThreadCurrentUserInfo, +sourceMessageID?: string, +repliesCount: number, +pinnedCount?: number, }; export type LegacyRawThreadInfos = { +[id: string]: LegacyRawThreadInfo, }; export const legacyRawThreadInfoValidator: TInterface = tShape({ id: tID, type: threadTypeValidator, name: t.maybe(t.String), avatar: t.maybe(clientAvatarValidator), description: t.maybe(t.String), color: t.String, creationTime: t.Number, parentThreadID: t.maybe(tID), containingThreadID: t.maybe(tID), community: t.maybe(tID), members: t.list(legacyMemberInfoValidator), roles: t.dict(tID, legacyRoleInfoValidator), currentUser: threadCurrentUserInfoValidator, sourceMessageID: t.maybe(tID), repliesCount: t.Number, pinnedCount: t.maybe(t.Number), }); export type RawThreadInfo = LegacyRawThreadInfo | MinimallyEncodedRawThreadInfo; export type RawThreadInfos = { +[id: string]: RawThreadInfo, }; export type MinimallyEncodedRawThreadInfos = { +[id: string]: MinimallyEncodedRawThreadInfo, }; export type LegacyThreadInfo = { +id: string, +type: ThreadType, +name: ?string, +uiName: string | ThreadEntity, +avatar?: ?ClientAvatar, +description: ?string, +color: string, // hex, without "#" or "0x" +creationTime: number, // millisecond timestamp +parentThreadID: ?string, +containingThreadID: ?string, +community: ?string, +members: $ReadOnlyArray, +roles: { +[id: string]: LegacyRoleInfo }, +currentUser: ThreadCurrentUserInfo, +sourceMessageID?: string, +repliesCount: number, +pinnedCount?: number, }; export const legacyThreadInfoValidator: TInterface = tShape({ id: tID, type: threadTypeValidator, name: t.maybe(t.String), uiName: t.union([t.String, threadEntityValidator]), avatar: t.maybe(clientAvatarValidator), description: t.maybe(t.String), color: t.String, creationTime: t.Number, parentThreadID: t.maybe(tID), containingThreadID: t.maybe(tID), community: t.maybe(tID), members: t.list(legacyRelativeMemberInfoValidator), roles: t.dict(tID, legacyRoleInfoValidator), currentUser: threadCurrentUserInfoValidator, sourceMessageID: t.maybe(tID), repliesCount: t.Number, pinnedCount: t.maybe(t.Number), }); export type ThreadInfo = LegacyThreadInfo | MinimallyEncodedThreadInfo; export type LegacyResolvedThreadInfo = { +id: string, +type: ThreadType, +name: ?string, +uiName: string, +avatar?: ?ClientAvatar, +description: ?string, +color: string, // hex, without "#" or "0x" +creationTime: number, // millisecond timestamp +parentThreadID: ?string, +containingThreadID: ?string, +community: ?string, +members: $ReadOnlyArray, +roles: { +[id: string]: LegacyRoleInfo }, +currentUser: ThreadCurrentUserInfo, +sourceMessageID?: string, +repliesCount: number, +pinnedCount?: number, }; export type ResolvedThreadInfo = | LegacyResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo; export type ServerMemberInfo = { +id: string, +role: ?string, +permissions: ThreadPermissionsInfo, +subscription: ThreadSubscription, +unread: ?boolean, +isSender: boolean, }; export type ServerThreadInfo = { +id: string, +type: ThreadType, +name: ?string, +avatar?: AvatarDBContent, +description: ?string, +color: string, // hex, without "#" or "0x" +creationTime: number, // millisecond timestamp +parentThreadID: ?string, +containingThreadID: ?string, +community: ?string, +depth: number, +members: $ReadOnlyArray, +roles: { +[id: string]: LegacyRoleInfo }, +sourceMessageID?: string, +repliesCount: number, +pinnedCount: number, }; -export type ThreadStore = { +export type LegacyThreadStore = { +threadInfos: RawThreadInfos, }; +export type ThreadStore = { + +threadInfos: MinimallyEncodedRawThreadInfos, +}; + export type ClientDBThreadInfo = { +id: string, +type: number, +name: ?string, +avatar?: ?string, +description: ?string, +color: string, +creationTime: string, +parentThreadID: ?string, +containingThreadID: ?string, +community: ?string, +members: string, +roles: string, +currentUser: string, +sourceMessageID?: string, +repliesCount: number, +pinnedCount?: number, }; export type ThreadDeletionRequest = { +threadID: string, +accountPassword?: empty, }; export type RemoveMembersRequest = { +threadID: string, +memberIDs: $ReadOnlyArray, }; export type RoleChangeRequest = { +threadID: string, +memberIDs: $ReadOnlyArray, +role: string, }; export type ChangeThreadSettingsResult = { +updatesResult: { +newUpdates: $ReadOnlyArray, }, +newMessageInfos: $ReadOnlyArray, }; export type ChangeThreadSettingsPayload = { +threadID: string, +updatesResult: { +newUpdates: $ReadOnlyArray, }, +newMessageInfos: $ReadOnlyArray, }; export type LeaveThreadRequest = { +threadID: string, }; export type LeaveThreadResult = { +updatesResult: { +newUpdates: $ReadOnlyArray, }, }; export type LeaveThreadPayload = { +updatesResult: { +newUpdates: $ReadOnlyArray, }, }; export type ThreadChanges = Partial<{ +type: ThreadType, +name: string, +description: string, +color: string, +parentThreadID: ?string, +newMemberIDs: $ReadOnlyArray, +avatar: UpdateUserAvatarRequest, }>; export type UpdateThreadRequest = { +threadID: string, +changes: ThreadChanges, +accountPassword?: empty, }; export type BaseNewThreadRequest = { +id?: ?string, +name?: ?string, +description?: ?string, +color?: ?string, +parentThreadID?: ?string, +initialMemberIDs?: ?$ReadOnlyArray, +ghostMemberIDs?: ?$ReadOnlyArray, }; type NewThreadRequest = | { +type: 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12, ...BaseNewThreadRequest, } | { +type: 5, +sourceMessageID: string, ...BaseNewThreadRequest, }; export type ClientNewThreadRequest = { ...NewThreadRequest, +calendarQuery: CalendarQuery, }; export type ServerNewThreadRequest = { ...NewThreadRequest, +calendarQuery?: ?CalendarQuery, }; export type NewThreadResponse = { +updatesResult: { +newUpdates: $ReadOnlyArray, }, +newMessageInfos: $ReadOnlyArray, +userInfos: UserInfos, +newThreadID: string, }; export type NewThreadResult = { +updatesResult: { +newUpdates: $ReadOnlyArray, }, +newMessageInfos: $ReadOnlyArray, +userInfos: UserInfos, +newThreadID: string, }; export type ServerThreadJoinRequest = { +threadID: string, +calendarQuery?: ?CalendarQuery, +inviteLinkSecret?: string, }; export type ClientThreadJoinRequest = { +threadID: string, +calendarQuery: CalendarQuery, +inviteLinkSecret?: string, }; export type ThreadJoinResult = { +updatesResult: { +newUpdates: $ReadOnlyArray, }, +rawMessageInfos: $ReadOnlyArray, +truncationStatuses: MessageTruncationStatuses, +userInfos: UserInfos, }; export type ThreadJoinPayload = { +updatesResult: { newUpdates: $ReadOnlyArray, }, +rawMessageInfos: $ReadOnlyArray, +truncationStatuses: MessageTruncationStatuses, +userInfos: $ReadOnlyArray, }; export type ThreadFetchMediaResult = { +media: $ReadOnlyArray, }; export type ThreadFetchMediaRequest = { +threadID: string, +limit: number, +offset: number, }; export type SidebarInfo = { +threadInfo: ThreadInfo, +lastUpdatedTime: number, +mostRecentNonLocalMessage: ?string, }; export type ToggleMessagePinRequest = { +messageID: string, +action: 'pin' | 'unpin', }; export type ToggleMessagePinResult = { +newMessageInfos: $ReadOnlyArray, +threadID: string, }; type CreateRoleAction = { +community: string, +name: string, +permissions: $ReadOnlyArray, +action: 'create_role', }; type EditRoleAction = { +community: string, +existingRoleID: string, +name: string, +permissions: $ReadOnlyArray, +action: 'edit_role', }; export type RoleModificationRequest = CreateRoleAction | EditRoleAction; export type RoleModificationResult = { +threadInfo: RawThreadInfo, +updatesResult: { +newUpdates: $ReadOnlyArray, }, }; export type RoleModificationPayload = { +threadInfo: RawThreadInfo, +updatesResult: { +newUpdates: $ReadOnlyArray, }, }; export type RoleDeletionRequest = { +community: string, +roleID: string, }; export type RoleDeletionResult = { +threadInfo: RawThreadInfo, +updatesResult: { +newUpdates: $ReadOnlyArray, }, }; export type RoleDeletionPayload = { +threadInfo: RawThreadInfo, +updatesResult: { +newUpdates: $ReadOnlyArray, }, }; // We can show a max of 3 sidebars inline underneath their parent in the chat // tab. If there are more, we show a button that opens a modal to see the rest export const maxReadSidebars = 3; // We can show a max of 5 sidebars inline underneath their parent // in the chat tab if every one of the displayed sidebars is unread export const maxUnreadSidebars = 5; export type ThreadStoreThreadInfos = LegacyRawThreadInfos; export type ChatMentionCandidate = { +threadInfo: ResolvedThreadInfo, +rawChatName: string | ThreadEntity, }; export type ChatMentionCandidates = { +[id: string]: ChatMentionCandidate, }; export type ChatMentionCandidatesObj = { +[id: string]: ChatMentionCandidates, }; export type UserProfileThreadInfo = { +threadInfo: ThreadInfo, +pendingPersonalThreadUserInfo?: UserInfo, }; diff --git a/web/types/redux-types.js b/web/types/redux-types.js index eb191ffc3..992f3d7f5 100644 --- a/web/types/redux-types.js +++ b/web/types/redux-types.js @@ -1,53 +1,53 @@ // @flow import type { EntryStore, CalendarQuery } from 'lib/types/entry-types.js'; import type { InviteLinksStore } from 'lib/types/link-types.js'; import type { MessageStore } from 'lib/types/message-types.js'; -import type { ThreadStore } from 'lib/types/thread-types.js'; +import type { LegacyThreadStore, ThreadStore } from 'lib/types/thread-types.js'; import type { CurrentUserInfo, UserInfos } from 'lib/types/user-types.js'; import type { URLInfo } from 'lib/utils/url-utils.js'; import type { NavInfo } from '../types/nav-types.js'; export type InitialReduxStateResponse = { +navInfo: NavInfo, +currentUserInfo: CurrentUserInfo, +entryStore: EntryStore, - +threadStore: ThreadStore, + +threadStore: LegacyThreadStore, +userInfos: UserInfos, +messageStore: MessageStore, +pushApiPublicKey: ?string, +commServicesAccessToken: null, +inviteLinksStore: InviteLinksStore, +keyserverInfo: InitialKeyserverInfo, }; export type InitialReduxState = { +navInfo: NavInfo, +currentUserInfo: CurrentUserInfo, +entryStore: EntryStore, +threadStore: ThreadStore, +userInfos: UserInfos, +messageStore: MessageStore, +pushApiPublicKey: ?string, +commServicesAccessToken: null, +inviteLinksStore: InviteLinksStore, +dataLoaded: boolean, +actualizedCalendarQuery: CalendarQuery, +keyserverInfos: { +[keyserverID: string]: InitialKeyserverInfo }, }; export type InitialKeyserverInfo = { +sessionID: ?string, +updatesCurrentAsOf: number, }; export type ExcludedData = { +threadStore?: boolean, }; export type InitialReduxStateRequest = { +urlInfo: URLInfo, +excludedData: ExcludedData, +clientUpdatesCurrentAsOf: number, };