diff --git a/keyserver/src/socket/session-utils.js b/keyserver/src/socket/session-utils.js --- a/keyserver/src/socket/session-utils.js +++ b/keyserver/src/socket/session-utils.js @@ -5,7 +5,10 @@ import type { TUnion } from 'tcomb'; import { hasMinCodeVersion } from 'lib/shared/version-utils.js'; -import type { UpdateActivityResult } from 'lib/types/activity-types.js'; +import type { + UpdateActivityResult, + ActivityUpdate, +} from 'lib/types/activity-types.js'; import type { IdentityKeysBlob } from 'lib/types/crypto-types.js'; import { isDeviceType } from 'lib/types/device-types.js'; import type { @@ -149,7 +152,7 @@ platformDetails.stateVersion === undefined)); const promises = []; - let activityUpdates = []; + let activityUpdates: Array = []; let stateCheckStatus = null; const clientSentPlatformDetails = clientResponses.some( response => response.type === serverRequestTypes.PLATFORM_DETAILS, @@ -243,14 +246,14 @@ } } - const activityUpdatePromise = (async () => { + const activityUpdatePromise: Promise = (async () => { if (activityUpdates.length === 0) { return undefined; } return await activityUpdater(viewer, { updates: activityUpdates }); })(); - const serverRequests = []; + const serverRequests: Array = []; const checkOneTimeKeysPromise = (async () => { if (!viewer.loggedIn) { @@ -429,7 +432,7 @@ } } - const fetchPromises = {}; + const fetchPromises: { [string]: Promise } = {}; for (const spec of values(serverStateSyncSpecs)) { if (shouldFetchAll[spec.hashKey]) { fetchPromises[spec.hashKey] = spec.fetch(viewer); @@ -450,9 +453,9 @@ .filter(spec => spec.innerHashSpec?.hashKey) .map(spec => [spec.innerHashSpec?.hashKey, spec]), ); - const hashesToCheck = {}, - failUnmentioned = {}, - stateChanges = {}; + const hashesToCheck: { [string]: number } = {}, + failUnmentioned: { [string]: boolean } = {}, + stateChanges: { [string]: mixed } = {}; for (const key of invalidKeys) { const spec = specPerHashKey[key]; const innerHashKey = spec?.innerHashSpec?.hashKey; @@ -461,7 +464,12 @@ // Instead of returning all the infos, we want to narrow down and figure // out which infos don't match first const infos = fetchedData[key]; - for (const infoID in infos) { + // We have a type error here because in fact the relationship between + // Infos and Info is not guaranteed to be like this. In particular, + // currentUserStateSyncSpec does not match this pattern. But this code + // doesn't fire for it because no innerHashSpec is defined + const iterableInfos: { +[string]: mixed } = (infos: any); + for (const infoID in iterableInfos) { let hashValue; if ( hasMinCodeVersion(viewer.platformDetails, { @@ -469,9 +477,11 @@ web: 32, }) ) { - hashValue = spec.getServerInfoHash(infos[infoID]); + // We have a type error here because Flow has no way to determine that + // spec and infos are matched up + hashValue = spec.getServerInfoHash((iterableInfos[infoID]: any)); } else { - hashValue = hash(infos[infoID]); + hashValue = hash(iterableInfos[infoID]); } hashesToCheck[`${innerHashKey}|${infoID}`] = hashValue; } @@ -486,27 +496,54 @@ continue; } const infos = fetchedData[innerSpec.hashKey]; - const info = infos[id]; - if (!info || innerHashSpec.additionalDeleteCondition?.(info)) { + // We have a type error here because in fact the relationship between + // Infos and Info is not guaranteed to be like this. In particular, + // currentUserStateSyncSpec does not match this pattern. But this code + // doesn't fire for it because no innerHashSpec is defined + const iterableInfos: { +[string]: mixed } = (infos: any); + const info = iterableInfos[id]; + // We have a type error here because Flow wants us to type iterableInfos + // in this file, but we don't have access to the parameterization of + // innerHashSpec here + if (!info || innerHashSpec.additionalDeleteCondition?.((info: any))) { if (!stateChanges[innerHashSpec.deleteKey]) { - stateChanges[innerHashSpec.deleteKey] = []; + stateChanges[innerHashSpec.deleteKey] = [id]; + } else { + // We have a type error here because in fact stateChanges values + // aren't always arrays. In particular, currentUserStateSyncSpec does + // not match this pattern. But this code doesn't fire for it because + // no innerHashSpec is defined + const curDeleteKeyChanges: Array = (stateChanges[ + innerHashSpec.deleteKey + ]: any); + curDeleteKeyChanges.push(id); } - stateChanges[innerHashSpec.deleteKey].push(id); continue; } if (!stateChanges[innerHashSpec.rawInfosKey]) { - stateChanges[innerHashSpec.rawInfosKey] = []; + stateChanges[innerHashSpec.rawInfosKey] = [info]; + } else { + // We have a type error here because in fact stateChanges values aren't + // always arrays. In particular, currentUserStateSyncSpec does not match + // this pattern. But this code doesn't fire for it because no + // innerHashSpec is defined + const curRawInfosKeyChanges: Array = (stateChanges[ + innerHashSpec.rawInfosKey + ]: any); + curRawInfosKeyChanges.push(info); } - stateChanges[innerHashSpec.rawInfosKey].push(info); } } - const checkStateRequest = { + // We have a type error here because the keys that get set on some of these + // collections aren't statically typed when they're set. Rather, they are set + // as arbitrary strings + const checkStateRequest: ServerCheckStateServerRequest = ({ type: serverRequestTypes.CHECK_STATE, hashesToCheck, failUnmentioned, stateChanges, - }; + }: any); if (Object.keys(hashesToCheck).length === 0) { return { checkStateRequest, sessionUpdate: { lastValidated: Date.now() } }; } else { diff --git a/keyserver/src/socket/socket.js b/keyserver/src/socket/socket.js --- a/keyserver/src/socket/socket.js +++ b/keyserver/src/socket/socket.js @@ -18,6 +18,7 @@ import { hasMinCodeVersion } from 'lib/shared/version-utils.js'; import type { Shape } from 'lib/types/core.js'; import { endpointIsSocketSafe } from 'lib/types/endpoints.js'; +import type { RawEntryInfo } from 'lib/types/entry-types.js'; import { defaultNumberPerThread } from 'lib/types/message-types.js'; import { redisMessageTypes, type RedisMessage } from 'lib/types/redis-types.js'; import { serverRequestTypes } from 'lib/types/request-types.js'; @@ -42,6 +43,8 @@ serverSocketMessageTypes, serverServerSocketMessageValidator, } from 'lib/types/socket-types.js'; +import type { RawThreadInfos } from 'lib/types/thread-types.js'; +import type { UserInfo, CurrentUserInfo } from 'lib/types/user-types.js'; import { ServerError } from 'lib/utils/errors.js'; import { values } from 'lib/utils/objects.js'; import { promiseAll } from 'lib/utils/promises.js'; @@ -284,7 +287,7 @@ invariant(clientSocketMessage, 'should be set'); const responseTo = clientSocketMessage.id; if (error.message === 'socket_deauthorized') { - const authErrorMessage: AuthErrorServerSocketMessage = { + let authErrorMessage: AuthErrorServerSocketMessage = { type: serverSocketMessageTypes.AUTH_ERROR, responseTo, message: error.message, @@ -294,10 +297,13 @@ // clients. Usually if the cookie is invalid we construct a new // anonymous Viewer with a new cookie, and then pass the cookie down // in the error. But we can't pass HTTP cookies in WebSocket messages. - authErrorMessage.sessionChange = { - cookie: this.viewer.cookiePairString, - currentUserInfo: { - anonymous: true, + authErrorMessage = { + ...authErrorMessage, + sessionChange: { + cookie: this.viewer.cookiePairString, + currentUserInfo: { + anonymous: true, + }, }, }; } @@ -316,7 +322,7 @@ }); } const { anonymousViewerData } = await promiseAll(promises); - const authErrorMessage: AuthErrorServerSocketMessage = { + let authErrorMessage: AuthErrorServerSocketMessage = { type: serverSocketMessageTypes.AUTH_ERROR, responseTo, message: error.message, @@ -330,10 +336,13 @@ // that only cookiePairString and cookieID are accessed on anonViewer // below. const anonViewer = new Viewer(anonymousViewerData); - authErrorMessage.sessionChange = { - cookie: anonViewer.cookiePairString, - currentUserInfo: { - anonymous: true, + authErrorMessage = { + ...authErrorMessage, + sessionChange: { + cookie: anonViewer.cookiePairString, + currentUserInfo: { + anonymous: true, + }, }, }; } @@ -514,13 +523,20 @@ isCookieMissingOlmNotificationsSession(viewer); if (!sessionInitializationResult.sessionContinued) { - const promises = Object.fromEntries( + const promises: { +[string]: Promise } = Object.fromEntries( values(serverStateSyncSpecs).map(spec => [ spec.hashKey, spec.fetchFullSocketSyncPayload(viewer, [calendarQuery]), ]), ); - const results = await promiseAll(promises); + // We have a type error here because Flow doesn't know spec.hashKey + const castPromises: { + +threadInfos: Promise, + +currentUserInfo: Promise, + +entryInfos: Promise<$ReadOnlyArray>, + +userInfos: Promise<$ReadOnlyArray>, + } = (promises: any); + const results = await promiseAll(castPromises); const payload: ServerStateSyncFullSocketPayload = { type: stateSyncPayloadTypes.FULL, messagesResult,