diff --git a/lib/keyserver-conn/keyserver-call-utils.js b/lib/keyserver-conn/keyserver-call-utils.js index 6c11e2e6d..8b406785e 100644 --- a/lib/keyserver-conn/keyserver-call-utils.js +++ b/lib/keyserver-conn/keyserver-call-utils.js @@ -1,93 +1,107 @@ // @flow import invariant from 'invariant'; import type { CalendarQuery } from '../types/entry-types.js'; import type { NotDeletedFilter } from '../types/filter-types.js'; function extractKeyserverIDFromID(id: string): string { return id.split('|')[0]; } function sortThreadIDsPerKeyserver(threadIDs: $ReadOnlyArray): { +[keyserverID: string]: $ReadOnlyArray, } { const results: { [string]: string[] } = {}; for (const threadID of threadIDs) { const keyserverID = extractKeyserverIDFromID(threadID); invariant(keyserverID, 'keyserver data missing from thread id'); if (results[keyserverID] === undefined) { results[keyserverID] = []; } results[keyserverID].push(threadID); } return results; } type CalendarThreadFilterWithWritableThreadIDs = { +type: 'threads', +threadIDs: string[], }; type CalendarFilterWithWritableThreadIDs = | NotDeletedFilter | CalendarThreadFilterWithWritableThreadIDs; type CalendarQueryWithWritableFilters = { +startDate: string, +endDate: string, +filters: CalendarFilterWithWritableThreadIDs[], }; function sortCalendarQueryPerKeyserver( calendarQuery: CalendarQuery, keyserverIDs: $ReadOnlyArray, ): { +[keyserverID: string]: CalendarQuery, } { const { startDate, endDate, filters } = calendarQuery; const results: { [string]: CalendarQueryWithWritableFilters } = {}; for (const keyserverID of keyserverIDs) { results[keyserverID] = { startDate, endDate, filters: [], }; } for (const filter of filters) { if (filter.type === 'not_deleted') { for (const keyserverID in results) { results[keyserverID].filters.push({ type: 'not_deleted' }); } } else if (filter.type === 'threads') { for (const threadID of filter.threadIDs) { const keyserverID = extractKeyserverIDFromID(threadID); if (results[keyserverID] === undefined) { continue; } let threadFilter = results[keyserverID].filters.find( flt => flt.type === 'threads', ); invariant( !threadFilter || threadFilter.type === 'threads', 'should only match CalendarThreadFilter', ); if (!threadFilter) { threadFilter = { type: 'threads', threadIDs: [] }; results[keyserverID].filters.push(threadFilter); } threadFilter.threadIDs.push(threadID); } } else { console.warn('unhandled filter in sortCalendarQueryPerKeyserver'); } } return results; } +function getThreadIDsForKeyservers( + threadIDs: $ReadOnlyArray, + keyserverIDs: $ReadOnlyArray, +): $ReadOnlyArray { + if (keyserverIDs.length === 0) { + return []; + } + const keyserverIDsSet = new Set(keyserverIDs); + return threadIDs.filter(threadID => + keyserverIDsSet.has(extractKeyserverIDFromID(threadID)), + ); +} + export { extractKeyserverIDFromID, sortThreadIDsPerKeyserver, sortCalendarQueryPerKeyserver, + getThreadIDsForKeyservers, }; diff --git a/lib/keyserver-conn/keyserver-call-utils.test.js b/lib/keyserver-conn/keyserver-call-utils.test.js index e4a59b7e3..a53069f89 100644 --- a/lib/keyserver-conn/keyserver-call-utils.test.js +++ b/lib/keyserver-conn/keyserver-call-utils.test.js @@ -1,88 +1,123 @@ // @flow import { extractKeyserverIDFromID, sortCalendarQueryPerKeyserver, + getThreadIDsForKeyservers, } from './keyserver-call-utils.js'; import type { CalendarQuery } from '../types/entry-types'; describe('extractKeyserverIDFromID', () => { it('should return for |', () => { const keyserverID = '404'; const id = keyserverID + '|1234'; expect(extractKeyserverIDFromID(id)).toBe(keyserverID); }); it('should return for ', () => { const keyserverID = '404'; expect(extractKeyserverIDFromID(keyserverID)).toBe(keyserverID); }); }); describe('sortCalendarQueryPerKeyserver', () => { it('should split the calendar query into multiple queries, one for every \ keyserver, that have all the properties of the original one, \ but only the thread ids that the keyserver should get', () => { const query: CalendarQuery = { startDate: '1463588881886', endDate: '1463588889886', filters: [ { type: 'not_deleted' }, { type: 'threads', threadIDs: ['256|1', '256|2', '100|100', '100|101'], }, ], }; const queriesPerKeyserver = { ['256']: { startDate: '1463588881886', endDate: '1463588889886', filters: [ { type: 'not_deleted' }, { type: 'threads', threadIDs: ['256|1', '256|2'], }, ], }, ['100']: { startDate: '1463588881886', endDate: '1463588889886', filters: [ { type: 'not_deleted' }, { type: 'threads', threadIDs: ['100|100', '100|101'], }, ], }, }; expect(sortCalendarQueryPerKeyserver(query, ['100', '256'])).toEqual( queriesPerKeyserver, ); }); it('should create calendar query for every keyserver in the second argument', () => { const query: CalendarQuery = { startDate: '1463588881886', endDate: '1463588889886', filters: [{ type: 'not_deleted' }], }; const queriesPerKeyserver = { ['256']: { startDate: '1463588881886', endDate: '1463588889886', filters: [{ type: 'not_deleted' }], }, ['100']: { startDate: '1463588881886', endDate: '1463588889886', filters: [{ type: 'not_deleted' }], }, }; expect(sortCalendarQueryPerKeyserver(query, ['100', '256'])).toEqual( queriesPerKeyserver, ); }); }); + +const keyserver1 = '256'; +const keyserver2 = '100'; +const keyserver3 = '200'; +const keyserver1ThreadIDs = [ + keyserver1 + '|1', + keyserver1 + '|2', + keyserver1 + '|3', +]; +const keyserver2ThreadIDs = [ + keyserver2 + '|1', + keyserver2 + '|2', + keyserver2 + '|3', +]; +const keyserver3ThreadIDs = [ + keyserver3 + '|1', + keyserver3 + '|2', + keyserver3 + '|3', +]; + +describe('getThreadIDsForKeyservers', () => { + it('should return thread belonging to specified keyservers', () => { + expect( + getThreadIDsForKeyservers( + [ + ...keyserver1ThreadIDs, + ...keyserver2ThreadIDs, + ...keyserver3ThreadIDs, + ], + [keyserver1, keyserver2], + ), + ).toEqual([...keyserver1ThreadIDs, ...keyserver2ThreadIDs]); + }); +}); diff --git a/lib/reducers/thread-reducer.js b/lib/reducers/thread-reducer.js index d2d34d4c1..4868cd09e 100644 --- a/lib/reducers/thread-reducer.js +++ b/lib/reducers/thread-reducer.js @@ -1,411 +1,500 @@ // @flow 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 { + keyserverAuthActionTypes, logOutActionTypes, deleteKeyserverAccountActionTypes, logInActionTypes, keyserverRegisterActionTypes, updateSubscriptionActionTypes, + deleteAccountActionTypes, } from '../actions/user-actions.js'; +import { getThreadIDsForKeyservers } from '../keyserver-conn/keyserver-call-utils.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 { RawThreadInfo } 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 { RawThreadInfos, ThreadStore } from '../types/thread-types.js'; import { type ClientUpdateInfo, processUpdatesActionType, } from '../types/update-types.js'; const { processStoreOperations: processThreadStoreOperations } = threadStoreOpsHandlers; function generateOpsForThreadUpdates( threadInfos: RawThreadInfos, 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] }, })), ]; const updatedThreadStore = processThreadStoreOperations( state, threadStoreOperations, ); return { threadStore: updatedThreadStore, newThreadInconsistencies: [], threadStoreOperations, }; + } else if (action.type === keyserverAuthActionTypes.success) { + const keyserverIDs = Object.keys(action.payload.updatesCurrentAsOf); + const threadIDsToRemove = getThreadIDsForKeyservers( + Object.keys(state.threadInfos), + keyserverIDs, + ); + const newThreadInfos = action.payload.threadInfos; + + const threadStoreOperations = [ + { + type: 'remove', + payload: { ids: threadIDsToRemove }, + }, + ...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 === deleteKeyserverAccountActionTypes.success || - (action.type === setNewSessionActionType && - action.payload.sessionChange.cookieInvalidated) + action.type === deleteAccountActionTypes.success ) { 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 === deleteKeyserverAccountActionTypes.success) { + const threadIDsToRemove = getThreadIDsForKeyservers( + Object.keys(state.threadInfos), + action.payload.keyserverIDs, + ); + + if (threadIDsToRemove.length === 0) { + return { + threadStore: state, + newThreadInconsistencies: [], + threadStoreOperations: [], + }; + } + + const threadStoreOperations = [ + { + type: 'remove', + payload: { ids: threadIDsToRemove }, + }, + ]; + const updatedThreadStore = processThreadStoreOperations( + state, + threadStoreOperations, + ); + return { + threadStore: updatedThreadStore, + newThreadInconsistencies: [], + threadStoreOperations, + }; + } else if ( + action.type === setNewSessionActionType && + action.payload.sessionChange.cookieInvalidated + ) { + const threadIDsToRemove = getThreadIDsForKeyservers( + Object.keys(state.threadInfos), + [action.payload.keyserverID], + ); + + if (threadIDsToRemove.length === 0) { + return { + threadStore: state, + newThreadInconsistencies: [], + threadStoreOperations: [], + }; + } + + const threadStoreOperations = [ + { + type: 'remove', + payload: { ids: threadIDsToRemove }, + }, + ]; + 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]; 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 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 } = {}; for (const [threadID, mostRecentTime] of threadIDToMostRecentTime) { const threadInfo = state.threadInfos[threadID]; if ( !threadInfo || threadInfo.currentUser.unread || action.payload.updatesCurrentAsOf > mostRecentTime ) { continue; } 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) { 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 } = {}; 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 threadInfo = state.threadInfos[threadID]; 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 if (action.type === setThreadUnreadStatusActionTypes.success) { const { threadID, resetToUnread } = action.payload; const threadInfo = state.threadInfos[threadID]; const { currentUser } = threadInfo; if (!resetToUnread || currentUser.unread) { return { threadStore: state, newThreadInconsistencies: [], threadStoreOperations: [], }; } 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, }; } else if (action.type === setClientDBStoreActionType) { return { threadStore: action.payload.threadStore ?? state, newThreadInconsistencies: [], threadStoreOperations: [], }; } return { threadStore: state, newThreadInconsistencies: [], threadStoreOperations: [], }; } export { reduceThreadInfos }; diff --git a/lib/types/account-types.js b/lib/types/account-types.js index f2dea4c37..0c7811b2b 100644 --- a/lib/types/account-types.js +++ b/lib/types/account-types.js @@ -1,268 +1,268 @@ // @flow import t, { type TInterface } from 'tcomb'; import type { SignedIdentityKeysBlob } from './crypto-types.js'; import type { PlatformDetails } from './device-types.js'; import type { CalendarQuery, CalendarResult, RawEntryInfo, } from './entry-types.js'; import { type RawMessageInfo, type MessageTruncationStatuses, type GenericMessagesResult, } from './message-types.js'; import type { PreRequestUserState } from './session-types.js'; import { type MixedRawThreadInfos, type RawThreadInfos, } from './thread-types.js'; import type { CurrentUserInfo, UserInfo, LoggedOutUserInfo, LoggedInUserInfo, } from './user-types'; import type { PolicyType } from '../facts/policies.js'; import { values } from '../utils/objects.js'; import { tShape } from '../utils/validation-utils.js'; export type ResetPasswordRequest = { +usernameOrEmail: string, }; export type LogOutResult = { +currentUserInfo: ?LoggedOutUserInfo, +preRequestUserState: PreRequestUserState, }; export type KeyserverLogOutResult = $ReadOnly<{ ...LogOutResult, +keyserverIDs: $ReadOnlyArray, }>; export type LogOutResponse = { +currentUserInfo: LoggedOutUserInfo, }; export type RegisterInfo = { ...LogInExtraInfo, +username: string, +password: string, }; export type DeviceTokenUpdateRequest = { +deviceToken: string, }; type DeviceTokenUpdateInput = { +[keyserverID: string]: DeviceTokenUpdateRequest, }; export type RegisterRequest = { +username: string, +email?: empty, +password: string, +calendarQuery?: ?CalendarQuery, +deviceTokenUpdateRequest?: ?DeviceTokenUpdateRequest, +platformDetails: PlatformDetails, +primaryIdentityPublicKey?: empty, +signedIdentityKeysBlob?: SignedIdentityKeysBlob, +initialNotificationsEncryptedMessage?: string, }; export type RegisterResponse = { +id: string, +rawMessageInfos: $ReadOnlyArray, +currentUserInfo: LoggedInUserInfo, +cookieChange: { +threadInfos: MixedRawThreadInfos, +userInfos: $ReadOnlyArray, }, }; export type RegisterResult = { +currentUserInfo: LoggedInUserInfo, +rawMessageInfos: $ReadOnlyArray, +threadInfos: RawThreadInfos, +userInfos: $ReadOnlyArray, +calendarQuery: CalendarQuery, }; export const logInActionSources = Object.freeze({ cookieInvalidationResolutionAttempt: 'COOKIE_INVALIDATION_RESOLUTION_ATTEMPT', appStartCookieLoggedInButInvalidRedux: 'APP_START_COOKIE_LOGGED_IN_BUT_INVALID_REDUX', appStartReduxLoggedInButInvalidCookie: 'APP_START_REDUX_LOGGED_IN_BUT_INVALID_COOKIE', socketAuthErrorResolutionAttempt: 'SOCKET_AUTH_ERROR_RESOLUTION_ATTEMPT', sqliteOpFailure: 'SQLITE_OP_FAILURE', sqliteLoadFailure: 'SQLITE_LOAD_FAILURE', logInFromWebForm: 'LOG_IN_FROM_WEB_FORM', logInFromNativeForm: 'LOG_IN_FROM_NATIVE_FORM', logInFromNativeSIWE: 'LOG_IN_FROM_NATIVE_SIWE', corruptedDatabaseDeletion: 'CORRUPTED_DATABASE_DELETION', refetchUserDataAfterAcknowledgment: 'REFETCH_USER_DATA_AFTER_ACKNOWLEDGMENT', keyserverAuthFromNative: 'KEYSERVER_AUTH_FROM_NATIVE', keyserverAuthFromWeb: 'KEYSERVER_AUTH_FROM_WEB', }); export type LogInActionSource = $Values; export type LogInStartingPayload = { +calendarQuery: CalendarQuery, +logInActionSource?: LogInActionSource, }; export type LogInExtraInfo = { +calendarQuery: CalendarQuery, +deviceTokenUpdateRequest: DeviceTokenUpdateInput, +signedIdentityKeysBlob?: SignedIdentityKeysBlob, +initialNotificationsEncryptedMessage?: string, }; export type LogInInfo = { ...LogInExtraInfo, +username: string, +password: string, +logInActionSource: LogInActionSource, +keyserverIDs?: $ReadOnlyArray, }; export type LogInRequest = { +usernameOrEmail?: ?string, +username?: ?string, +password: string, +calendarQuery?: ?CalendarQuery, +deviceTokenUpdateRequest?: ?DeviceTokenUpdateRequest, +platformDetails: PlatformDetails, +watchedIDs: $ReadOnlyArray, +source?: LogInActionSource, +primaryIdentityPublicKey?: empty, +signedIdentityKeysBlob?: SignedIdentityKeysBlob, +initialNotificationsEncryptedMessage?: string, }; export type ServerLogInResponse = { +currentUserInfo: LoggedInUserInfo, +rawMessageInfos: $ReadOnlyArray, +truncationStatuses: MessageTruncationStatuses, +userInfos: $ReadOnlyArray, +rawEntryInfos?: ?$ReadOnlyArray, +serverTime: number, +cookieChange: { +threadInfos: MixedRawThreadInfos, +userInfos: $ReadOnlyArray, }, +notAcknowledgedPolicies?: $ReadOnlyArray, }; export type ClientLogInResponse = $ReadOnly<{ ...ServerLogInResponse, +cookieChange: $ReadOnly<{ ...$PropertyType, threadInfos: RawThreadInfos, }>, }>; export type LogInResult = { +threadInfos: RawThreadInfos, +currentUserInfo: LoggedInUserInfo, +messagesResult: GenericMessagesResult, +userInfos: $ReadOnlyArray, +calendarResult: CalendarResult, +updatesCurrentAsOf: { +[keyserverID: string]: number }, +logInActionSource: LogInActionSource, +notAcknowledgedPolicies?: $ReadOnlyArray, }; export type KeyserverAuthResult = { - +threadInfos: MixedRawThreadInfos, + +threadInfos: RawThreadInfos, +currentUserInfo?: ?LoggedInUserInfo, +messagesResult: GenericMessagesResult, +userInfos: $ReadOnlyArray, +calendarResult: CalendarResult, +updatesCurrentAsOf: { +[keyserverID: string]: number }, +logInActionSource: LogInActionSource, +notAcknowledgedPolicies?: ?$ReadOnlyArray, +preRequestUserInfo: ?CurrentUserInfo, }; type KeyserverRequestData = { +initialContentEncryptedMessage: string, +initialNotificationsEncryptedMessage: string, }; export type KeyserverAuthInfo = { +userID: string, +deviceID: string, +doNotRegister: boolean, +calendarQuery: CalendarQuery, +deviceTokenUpdateInput: DeviceTokenUpdateInput, +logInActionSource: LogInActionSource, +keyserverData: { +[keyserverID: string]: KeyserverRequestData }, }; export type KeyserverAuthRequest = $ReadOnly<{ ...KeyserverRequestData, +userID: string, +deviceID: string, +doNotRegister: boolean, +calendarQuery: CalendarQuery, +deviceTokenUpdateRequest?: ?DeviceTokenUpdateRequest, +watchedIDs: $ReadOnlyArray, +platformDetails: PlatformDetails, +source?: LogInActionSource, }>; export type UpdatePasswordRequest = { code: string, password: string, calendarQuery?: ?CalendarQuery, deviceTokenUpdateRequest?: ?DeviceTokenUpdateRequest, platformDetails: PlatformDetails, watchedIDs: $ReadOnlyArray, }; export type PolicyAcknowledgmentRequest = { +policy: PolicyType, }; export type EmailSubscriptionRequest = { +email: string, }; export type UpdateUserSettingsRequest = { +name: 'default_user_notifications', +data: NotificationTypes, }; export const userSettingsTypes = Object.freeze({ DEFAULT_NOTIFICATIONS: 'default_user_notifications', }); export const notificationTypes = Object.freeze({ FOCUSED: 'focused', BADGE_ONLY: 'badge_only', BACKGROUND: 'background', }); export type NotificationTypes = $Values; export const notificationTypeValues: $ReadOnlyArray = values(notificationTypes); export type DefaultNotificationPayload = { +default_user_notifications: ?NotificationTypes, }; export const defaultNotificationPayloadValidator: TInterface = tShape({ default_user_notifications: t.maybe(t.enums.of(notificationTypeValues)), }); export type ClaimUsernameResponse = { +message: string, +signature: string, };