diff --git a/lib/actions/upload-actions.js b/lib/actions/upload-actions.js
--- a/lib/actions/upload-actions.js
+++ b/lib/actions/upload-actions.js
@@ -46,7 +46,7 @@
     const abortHandler = callbacks && callbacks.abortHandler;
     const uploadBlob = callbacks && callbacks.uploadBlob;
 
-    const stringExtras = {};
+    const stringExtras: { [string]: string } = {};
     if (extras.height !== null && extras.height !== undefined) {
       stringExtras.height = extras.height.toString();
     }
@@ -71,8 +71,8 @@
     const response = await callServerEndpoint(
       'upload_multimedia',
       {
-        multimedia: [multimedia],
         ...stringExtras,
+        multimedia: [multimedia],
       },
       {
         onProgress,
diff --git a/lib/actions/user-actions.js b/lib/actions/user-actions.js
--- a/lib/actions/user-actions.js
+++ b/lib/actions/user-actions.js
@@ -182,7 +182,7 @@
   };
 
 function mergeUserInfos(...userInfoArrays: UserInfo[][]): UserInfo[] {
-  const merged = {};
+  const merged: { [string]: UserInfo } = {};
   for (const userInfoArray of userInfoArrays) {
     for (const userInfo of userInfoArray) {
       merged[userInfo.id] = userInfo;
diff --git a/lib/components/chat-mention-provider.react.js b/lib/components/chat-mention-provider.react.js
--- a/lib/components/chat-mention-provider.react.js
+++ b/lib/components/chat-mention-provider.react.js
@@ -81,9 +81,9 @@
   chatMentionCandidatesObj: ChatMentionCandidatesObj,
   communityThreadIDForGenesisThreads: { +[id: string]: string },
 } {
-  const result = {};
+  const result: { [string]: { [string]: ResolvedThreadInfo } } = {};
   const visitedGenesisThreads = new Set();
-  const communityThreadIDForGenesisThreads = {};
+  const communityThreadIDForGenesisThreads: { [string]: string } = {};
   for (const currentThreadID in threadInfos) {
     const currentThreadInfo = threadInfos[currentThreadID];
     const { community: currentThreadCommunity } = currentThreadInfo;
@@ -94,9 +94,9 @@
       continue;
     }
     if (!result[currentThreadCommunity]) {
-      result[currentThreadCommunity] = {
-        [currentThreadCommunity]: threadInfos[currentThreadCommunity],
-      };
+      result[currentThreadCommunity] = {};
+      result[currentThreadCommunity][currentThreadCommunity] =
+        threadInfos[currentThreadCommunity];
     }
     // Handle GENESIS community case: mentioning inside GENESIS should only
     // show chats and threads inside the top level that is below GENESIS.
@@ -214,7 +214,7 @@
   +[id: string]: SentencePrefixSearchIndex,
 } {
   return React.useMemo(() => {
-    const result = {};
+    const result: { [string]: SentencePrefixSearchIndex } = {};
     for (const communityThreadID in chatMentionCandidatesObj) {
       const searchIndex = new SentencePrefixSearchIndex();
       const searchIndexEntries = [];
diff --git a/lib/permissions/thread-permissions.js b/lib/permissions/thread-permissions.js
--- a/lib/permissions/thread-permissions.js
+++ b/lib/permissions/thread-permissions.js
@@ -1,5 +1,7 @@
 // @flow
 
+import invariant from 'invariant';
+
 import {
   parseThreadPermissionString,
   includeThreadPermissionForThreadType,
@@ -35,19 +37,24 @@
   permissions: ?ThreadPermissionsBlob,
   threadID: string,
 ): ThreadPermissionsInfo {
-  const result = {};
+  const result: { [permission: ThreadPermission]: ThreadPermissionInfo } = {};
   for (const permissionName in threadPermissions) {
     const permissionKey = threadPermissions[permissionName];
     const permission = permissionLookup(permissions, permissionKey);
-    let source = null;
+    let entry: ThreadPermissionInfo = { value: false, source: null };
     if (permission) {
-      if (permissions && permissions[permissionKey]) {
-        source = permissions[permissionKey].source;
+      const blobEntry = permissions ? permissions[permissionKey] : null;
+      if (blobEntry) {
+        invariant(
+          blobEntry.value,
+          'permissionLookup returned true but blob had false permission!',
+        );
+        entry = { value: true, source: blobEntry.source };
       } else {
-        source = threadID;
+        entry = { value: true, source: threadID };
       }
     }
-    result[permissionKey] = { value: permission, source };
+    result[permissionKey] = entry;
   }
   return result;
 }
@@ -62,7 +69,7 @@
   threadID: string,
   threadType: ThreadType,
 ): ?ThreadPermissionsBlob {
-  let permissions = {};
+  let permissions: { [permission: string]: ThreadPermissionInfo } = {};
 
   if (permissionsFromParent) {
     for (const permissionKey in permissionsFromParent) {
@@ -116,7 +123,8 @@
   if (!permissions) {
     return null;
   }
-  const permissionsForChildren = {};
+  const permissionsForChildren: { [permission: string]: ThreadPermissionInfo } =
+    {};
   for (const permissionKey in permissions) {
     const permissionValue = permissions[permissionKey];
     const parsed = parseThreadPermissionString(permissionKey);
diff --git a/lib/reducers/draft-reducer.js b/lib/reducers/draft-reducer.js
--- a/lib/reducers/draft-reducer.js
+++ b/lib/reducers/draft-reducer.js
@@ -73,7 +73,7 @@
       },
     };
   } else if (action.type === setClientDBStoreActionType) {
-    const drafts = {};
+    const drafts: { [string]: string } = {};
     for (const dbDraftInfo of action.payload.drafts) {
       drafts[dbDraftInfo.key] = dbDraftInfo.text;
     }
diff --git a/lib/reducers/entry-reducer.js b/lib/reducers/entry-reducer.js
--- a/lib/reducers/entry-reducer.js
+++ b/lib/reducers/entry-reducer.js
@@ -92,7 +92,7 @@
   newEntryInfos: $ReadOnlyArray<RawEntryInfo>,
   threadInfos: RawThreadInfos,
 ) {
-  const mergedEntryInfos = {};
+  const mergedEntryInfos: { [string]: RawEntryInfo } = {};
   let someEntryUpdated = false;
 
   for (const rawEntryInfo of newEntryInfos) {
diff --git a/lib/reducers/invite-links-reducer.js b/lib/reducers/invite-links-reducer.js
--- a/lib/reducers/invite-links-reducer.js
+++ b/lib/reducers/invite-links-reducer.js
@@ -9,7 +9,7 @@
   deleteAccountActionTypes,
   logOutActionTypes,
 } from '../actions/user-actions.js';
-import type { InviteLinksStore } from '../types/link-types.js';
+import type { InviteLinksStore, CommunityLinks } from '../types/link-types.js';
 import type { BaseAction } from '../types/redux-types.js';
 import { setNewSessionActionType } from '../utils/action-utils.js';
 
@@ -18,7 +18,7 @@
   action: BaseAction,
 ): InviteLinksStore {
   if (action.type === fetchPrimaryInviteLinkActionTypes.success) {
-    const links = {};
+    const links: { [string]: CommunityLinks } = {};
     for (const link of action.payload.links) {
       links[link.communityID] = {
         ...state.links[link.communityID],
diff --git a/lib/reducers/message-reducer.js b/lib/reducers/message-reducer.js
--- a/lib/reducers/message-reducer.js
+++ b/lib/reducers/message-reducer.js
@@ -218,7 +218,7 @@
     pendingToRealizedThreadIDsSelector(threadInfos);
 
   const messageStoreOperations: MessageStoreOperation[] = [];
-  const messages = {};
+  const messages: { [string]: RawMessageInfo } = {};
   for (const storeMessageID in messageStore.messages) {
     const message = messageStore.messages[storeMessageID];
     const newThreadID = pendingToRealizedThreadIDs.get(message.threadID);
@@ -243,9 +243,9 @@
     messageStoreOperations.push(updateMsgOperation);
   }
 
-  const threads = {};
+  const threads: { [string]: ThreadMessageInfo } = {};
   const reassignedThreadIDs = [];
-  const updatedThreads = {};
+  const updatedThreads: { [string]: ThreadMessageInfo } = {};
   const threadsToRemove = [];
   for (const threadID in messageStore.threads) {
     const threadMessageInfo = messageStore.threads[threadID];
@@ -304,7 +304,7 @@
 function mergeNewMessages(
   oldMessageStore: MessageStore,
   newMessageInfos: $ReadOnlyArray<RawMessageInfo>,
-  truncationStatus: { [threadID: string]: MessageTruncationStatus },
+  truncationStatus: { +[threadID: string]: MessageTruncationStatus },
   threadInfos: RawThreadInfos,
 ): MergeNewMessagesResult {
   const {
@@ -455,8 +455,8 @@
   );
   const oldMessageInfosToCombine = [];
   const threadsThatNeedMessageIDsResorted = [];
-  const local = {};
-  const updatedThreads = {};
+  const local: { [string]: LocalMessageInfo } = {};
+  const updatedThreads: { [string]: ThreadMessageInfo } = {};
   const threads = _flow(
     _mapValuesWithKeys((messageIDs: string[], threadID: string) => {
       const oldThread = updatedMessageStore.threads[threadID];
@@ -634,7 +634,7 @@
   messageStoreOperations.push(...reassignMessagesOps);
   const watchedIDs = [...threadWatcher.getWatchedIDs(), ...reassignedThreadIDs];
 
-  const filteredThreads = {};
+  const filteredThreads: { [string]: ThreadMessageInfo } = {};
   const threadsToRemoveMessagesFrom = [];
   const messageIDsToRemove = [];
   for (const threadID in reassignedMessageStore.threads) {
@@ -648,7 +648,7 @@
     }
   }
 
-  const updatedThreads = {};
+  const updatedThreads: { [string]: ThreadMessageInfo } = {};
   for (const threadID in threadInfos) {
     const threadInfo = threadInfos[threadID];
     if (
@@ -860,7 +860,7 @@
       newThreadInfos,
     );
   } else if (action.type === registerActionTypes.success) {
-    const truncationStatuses = {};
+    const truncationStatuses: { [string]: MessageTruncationStatus } = {};
     for (const messageInfo of action.payload.rawMessageInfos) {
       truncationStatuses[messageInfo.threadID] =
         messageTruncationStatus.EXHAUSTIVE;
@@ -909,7 +909,7 @@
     );
   } else if (action.type === sendEditMessageActionTypes.success) {
     const { newMessageInfos } = action.payload;
-    const truncationStatuses = {};
+    const truncationStatuses: { [string]: MessageTruncationStatus } = {};
     for (const messageInfo of newMessageInfos) {
       truncationStatuses[messageInfo.threadID] =
         messageTruncationStatus.UNCHANGED;
@@ -1136,7 +1136,7 @@
       },
     };
   } else if (action.type === saveMessagesActionType) {
-    const truncationStatuses = {};
+    const truncationStatuses: { [string]: MessageTruncationStatus } = {};
     for (const messageInfo of action.payload.rawMessageInfos) {
       truncationStatuses[messageInfo.threadID] =
         messageTruncationStatus.UNCHANGED;
@@ -1163,7 +1163,7 @@
     const now = Date.now();
     const messageIDsToPrune = [];
 
-    const updatedThreads = {};
+    const updatedThreads: { [string]: ThreadMessageInfo } = {};
     for (const threadID of action.payload.threadIDs) {
       let thread = messageStore.threads[threadID];
       if (!thread) {
@@ -1535,7 +1535,7 @@
     // When starting the app on native, we filter out any local-only multimedia
     // messages because the relevant context is no longer available
     const messageIDsToBeRemoved = [];
-    const threadsToAdd = {};
+    const threadsToAdd: { [string]: ThreadMessageInfo } = {};
     for (const id in actionPayloadMessages) {
       const message = actionPayloadMessages[id];
       const { threadID } = message;
diff --git a/lib/reducers/thread-reducer.js b/lib/reducers/thread-reducer.js
--- a/lib/reducers/thread-reducer.js
+++ b/lib/reducers/thread-reducer.js
@@ -41,7 +41,11 @@
   fullStateSyncActionType,
   incrementalStateSyncActionType,
 } from '../types/socket-types.js';
-import type { RawThreadInfos, ThreadStore } from '../types/thread-types.js';
+import type {
+  RawThreadInfo,
+  RawThreadInfos,
+  ThreadStore,
+} from '../types/thread-types.js';
 import {
   type ClientUpdateInfo,
   processUpdatesActionType,
@@ -293,7 +297,7 @@
       threadStoreOperations,
     };
   } else if (action.type === updateActivityActionTypes.success) {
-    const updatedThreadInfos = {};
+    const updatedThreadInfos: { [string]: RawThreadInfo } = {};
     for (const setToUnread of action.payload.result.unfocusedToUnread) {
       const threadInfo = state.threadInfos[setToUnread];
       if (threadInfo && !threadInfo.currentUser.unread) {
diff --git a/lib/selectors/chat-selectors.js b/lib/selectors/chat-selectors.js
--- a/lib/selectors/chat-selectors.js
+++ b/lib/selectors/chat-selectors.js
@@ -481,7 +481,7 @@
     );
 
     const renderedReactions: ReactionInfo = (() => {
-      const result = {};
+      const result: { [string]: MessageReactionInfo } = {};
 
       let messageReactsMap;
       if (originalMessageInfo.id) {
diff --git a/lib/selectors/invite-links-selectors.js b/lib/selectors/invite-links-selectors.js
--- a/lib/selectors/invite-links-selectors.js
+++ b/lib/selectors/invite-links-selectors.js
@@ -10,7 +10,7 @@
 } = createSelector(
   (state: AppState) => state.inviteLinksStore.links,
   (links: InviteLinks) => {
-    const primaryLinks = {};
+    const primaryLinks: { [string]: InviteLink } = {};
     for (const communityID in links) {
       const communityLinks = links[communityID];
       if (communityLinks.primaryLink) {
diff --git a/lib/selectors/socket-selectors.js b/lib/selectors/socket-selectors.js
--- a/lib/selectors/socket-selectors.js
+++ b/lib/selectors/socket-selectors.js
@@ -108,7 +108,7 @@
         } else if (serverRequest.type === serverRequestTypes.CHECK_STATE) {
           const query = calendarQuery(calendarActive);
 
-          const hashResults = {};
+          const hashResults: { [string]: boolean } = {};
           for (const key in serverRequest.hashesToCheck) {
             const expectedHashValue = serverRequest.hashesToCheck[key];
             let hashValue;
diff --git a/lib/selectors/thread-selectors.js b/lib/selectors/thread-selectors.js
--- a/lib/selectors/thread-selectors.js
+++ b/lib/selectors/thread-selectors.js
@@ -158,7 +158,7 @@
     onScreen: $ReadOnlyArray<ThreadInfo>,
     includeDeleted: boolean,
   ) => {
-    const allDaysWithinRange = {},
+    const allDaysWithinRange: { [string]: string[] } = {},
       startDate = dateFromString(startDateString),
       endDate = dateFromString(endDateString);
     for (
@@ -188,7 +188,7 @@
 } = createSelector(
   threadInfoSelector,
   (threadInfos: { +[id: string]: ThreadInfo }) => {
-    const result = {};
+    const result: { [string]: ThreadInfo[] } = {};
     for (const id in threadInfos) {
       const threadInfo = threadInfos[id];
       const parentThreadID = threadInfo.parentThreadID;
@@ -196,7 +196,7 @@
         continue;
       }
       if (result[parentThreadID] === undefined) {
-        result[parentThreadID] = [];
+        result[parentThreadID] = ([]: ThreadInfo[]);
       }
       result[parentThreadID].push(threadInfo);
     }
@@ -209,7 +209,7 @@
 } = createSelector(
   threadInfoSelector,
   (threadInfos: { +[id: string]: ThreadInfo }) => {
-    const result = {};
+    const result: { [string]: ThreadInfo[] } = {};
     for (const id in threadInfos) {
       const threadInfo = threadInfos[id];
       const { containingThreadID } = threadInfo;
@@ -217,7 +217,7 @@
         continue;
       }
       if (result[containingThreadID] === undefined) {
-        result[containingThreadID] = [];
+        result[containingThreadID] = ([]: ThreadInfo[]);
       }
       result[containingThreadID].push(threadInfo);
     }
@@ -400,7 +400,7 @@
   ) => {
     const pendingToRealizedThreadIDs =
       pendingToRealizedThreadIDsSelector(rawThreadInfos);
-    const result = {};
+    const result: { [string]: ThreadInfo } = {};
     for (const realizedID of pendingToRealizedThreadIDs.values()) {
       const threadInfo = threadInfos[realizedID];
       if (threadInfo && threadInfo.sourceMessageID) {
diff --git a/lib/shared/entry-utils.js b/lib/shared/entry-utils.js
--- a/lib/shared/entry-utils.js
+++ b/lib/shared/entry-utils.js
@@ -102,7 +102,7 @@
   calendarQuery: CalendarQuery,
 ): { +[id: string]: RawEntryInfo } {
   let filtered = false;
-  const filteredRawEntryInfos = {};
+  const filteredRawEntryInfos: { [string]: RawEntryInfo } = {};
   for (const id in rawEntryInfos) {
     const rawEntryInfo = rawEntryInfos[id];
     if (!rawEntryInfoWithinCalendarQuery(rawEntryInfo, calendarQuery)) {
@@ -228,7 +228,7 @@
 function serverEntryInfosObject(
   array: $ReadOnlyArray<RawEntryInfo>,
 ): RawEntryInfos {
-  const obj = {};
+  const obj: { [string]: RawEntryInfo } = {};
   for (const rawEntryInfo of array) {
     const entryInfo = serverEntryInfo(rawEntryInfo);
     if (!entryInfo) {
diff --git a/lib/shared/messages/add-members-message-spec.js b/lib/shared/messages/add-members-message-spec.js
--- a/lib/shared/messages/add-members-message-spec.js
+++ b/lib/shared/messages/add-members-message-spec.js
@@ -118,7 +118,7 @@
     threadInfo: ThreadInfo,
     params: NotificationTextsParams,
   ): Promise<NotifTexts> {
-    const addedMembersObject = {};
+    const addedMembersObject: { [string]: RelativeUserInfo } = {};
     for (const messageInfo of messageInfos) {
       invariant(
         messageInfo.type === messageTypes.ADD_MEMBERS,
diff --git a/lib/shared/messages/change-role-message-spec.js b/lib/shared/messages/change-role-message-spec.js
--- a/lib/shared/messages/change-role-message-spec.js
+++ b/lib/shared/messages/change-role-message-spec.js
@@ -152,7 +152,7 @@
     threadInfo: ThreadInfo,
     params: NotificationTextsParams,
   ): Promise<NotifTexts> {
-    const membersObject = {};
+    const membersObject: { [string]: RelativeUserInfo } = {};
     for (const messageInfo of messageInfos) {
       invariant(
         messageInfo.type === messageTypes.CHANGE_ROLE,
diff --git a/lib/shared/messages/join-thread-message-spec.js b/lib/shared/messages/join-thread-message-spec.js
--- a/lib/shared/messages/join-thread-message-spec.js
+++ b/lib/shared/messages/join-thread-message-spec.js
@@ -91,7 +91,7 @@
     messageInfos: $ReadOnlyArray<MessageInfo>,
     threadInfo: ThreadInfo,
   ): Promise<NotifTexts> {
-    const joinerArray = {};
+    const joinerArray: { [string]: RelativeUserInfo } = {};
     for (const messageInfo of messageInfos) {
       invariant(
         messageInfo.type === messageTypes.JOIN_THREAD,
diff --git a/lib/shared/messages/leave-thread-message-spec.js b/lib/shared/messages/leave-thread-message-spec.js
--- a/lib/shared/messages/leave-thread-message-spec.js
+++ b/lib/shared/messages/leave-thread-message-spec.js
@@ -91,7 +91,7 @@
     messageInfos: $ReadOnlyArray<MessageInfo>,
     threadInfo: ThreadInfo,
   ): Promise<NotifTexts> {
-    const leaverBeavers = {};
+    const leaverBeavers: { [string]: RelativeUserInfo } = {};
     for (const messageInfo of messageInfos) {
       invariant(
         messageInfo.type === messageTypes.LEAVE_THREAD,
diff --git a/lib/shared/messages/remove-members-message-spec.js b/lib/shared/messages/remove-members-message-spec.js
--- a/lib/shared/messages/remove-members-message-spec.js
+++ b/lib/shared/messages/remove-members-message-spec.js
@@ -118,7 +118,7 @@
     threadInfo: ThreadInfo,
     params: NotificationTextsParams,
   ): Promise<NotifTexts> {
-    const removedMembersObject = {};
+    const removedMembersObject: { [string]: RelativeUserInfo } = {};
     for (const messageInfo of messageInfos) {
       invariant(
         messageInfo.type === messageTypes.REMOVE_MEMBERS,
diff --git a/lib/shared/thread-utils.js b/lib/shared/thread-utils.js
--- a/lib/shared/thread-utils.js
+++ b/lib/shared/thread-utils.js
@@ -482,7 +482,7 @@
     pinnedCount: 0,
   };
 
-  const userInfos = {};
+  const userInfos: { [string]: UserInfo } = {};
   for (const member of members) {
     const { id, username } = member;
     userInfos[id] = { id, username };
@@ -1780,7 +1780,7 @@
   threadInfo: ThreadInfo | MinimallyEncodedThreadInfo,
 ): RoleAndMemberCount {
   return React.useMemo(() => {
-    const roleIDsToNames = {};
+    const roleIDsToNames: { [string]: string } = {};
 
     Object.keys(threadInfo.roles).forEach(roleID => {
       roleIDsToNames[roleID] = threadInfo.roles[roleID].name;
@@ -1817,7 +1817,8 @@
   threadInfo: ThreadInfo,
 ): RoleUserSurfacedPermissions {
   return React.useMemo(() => {
-    const roleNamesToPermissions = {};
+    const roleNamesToPermissions: { [string]: Set<UserSurfacedPermission> } =
+      {};
 
     Object.keys(threadInfo.roles).forEach(roleID => {
       const roleName = threadInfo.roles[roleID].name;
diff --git a/lib/socket/socket.react.js b/lib/socket/socket.react.js
--- a/lib/socket/socket.react.js
+++ b/lib/socket/socket.react.js
@@ -164,7 +164,7 @@
     });
 
     const socket = this.props.openSocket();
-    const openObject = {};
+    const openObject: { initializeMessageSent?: true } = {};
     socket.onopen = () => {
       if (this.socket === socket) {
         this.initializeSocket();
@@ -548,7 +548,6 @@
       'inflightRequests falsey inside sendInitialMessage',
     );
     const messageID = this.nextClientMessageID++;
-    const promises = {};
 
     const shouldSendInitialPlatformDetails = !_isEqual(
       this.props.lastCommunicatedPlatformDetails,
@@ -562,13 +561,14 @@
       });
     }
 
+    let activityUpdatePromise;
     const { queuedActivityUpdates } = this.props.connection;
     if (queuedActivityUpdates.length > 0) {
       clientResponses.push({
         type: serverRequestTypes.INITIAL_ACTIVITY_UPDATES,
         activityUpdates: queuedActivityUpdates,
       });
-      promises.activityUpdateMessage = inflightRequests.fetchResponse(
+      activityUpdatePromise = inflightRequests.fetchResponse(
         messageID,
         serverSocketMessageTypes.ACTIVITY_UPDATE_RESPONSE,
       );
@@ -588,14 +588,17 @@
     this.initializedWithUserState = this.props.preRequestUserState;
     this.sendMessage(initialMessage);
 
-    promises.stateSyncMessage = inflightRequests.fetchResponse(
+    const stateSyncPromise = inflightRequests.fetchResponse(
       messageID,
       serverSocketMessageTypes.STATE_SYNC,
     );
 
-    const { stateSyncMessage, activityUpdateMessage } = await promiseAll(
-      promises,
-    );
+    // https://flow.org/try/#1N4Igxg9gdgZglgcxALlAJwKYEMwBcD6aArlLnALYYrgA2WAzvXGCADQgYAeOBARgJ74AJhhhYiNXClzEM7DFCLl602QF92kEdQb8oYAAQwSeONAMAHNBHJx6GAII0aAHgAqyA8AMBqANoA1hj8nvQycFAIALqetpwYQgZqAHwAFAA6UAYGERZEuJ4AJABK2EIA8lA0-O7JrJkAlJ4ACta29i6F5bwAVgCyWBburAa4-BYYEDAGhVgA7lhwuMnJXpnZkFBhlm12GPQGALwGflEA3OsGm9tB-AfH3T0YeAB0t-SpufkNF1lGEGgDKkaBhcDkjgYAAxncEuAzvF4gyK4AAWMLgPh8DTWfw20BuwQh7z8cHOlzxWzBVhsewhX1wgWCZNxOxp9noLzy9BRqWp7QwP0uaku1zBmHoElw9wM80WYNabIwLywzl5u3Zgr+ooMAgAclhKJ5gH4wmgItFYnB4kI1BDgGpftkYACgSCwXAIdDYfDghykQhUejMdjgOSrviwbcib6Sczstk9QaMIz+FEIeLJfRY46kpdMLgiGgsonKL9hVBMrp9EYTGRzPYoEIAJJQJZwFV9fb0LAIDCpEOXN2jfa4BX8nNwaYZEAojDOCDpEAvMJYNBSgDqSx5i4Ci4aA5ZuBHY9pxxP9he4ogNAAbn2ZEQBTny5dZUtWfynDRUt4j2FzxgSSamobAgHeaBMNA1A3pCLwAEwAIwACwvJCIBqEAA
+    // $FlowFixMe fixed in Flow 0.214
+    const { stateSyncMessage, activityUpdateMessage } = await promiseAll({
+      activityUpdateMessage: activityUpdatePromise,
+      stateSyncMessage: stateSyncPromise,
+    });
 
     if (shouldSendInitialPlatformDetails) {
       this.props.dispatch({
diff --git a/lib/utils/conversion-utils.js b/lib/utils/conversion-utils.js
--- a/lib/utils/conversion-utils.js
+++ b/lib/utils/conversion-utils.js
@@ -88,7 +88,7 @@
   }
   if (validator.meta.kind === 'interface' && typeof input === 'object') {
     const recastValidator: TInterface<typeof input> = (validator: any);
-    const result = {};
+    const result: { [string]: mixed } = {};
     for (const key in input) {
       const innerValidator = recastValidator.meta.props[key];
       result[key] = convertObject(
diff --git a/lib/utils/cookie-utils.js b/lib/utils/cookie-utils.js
--- a/lib/utils/cookie-utils.js
+++ b/lib/utils/cookie-utils.js
@@ -3,7 +3,7 @@
 function parseCookies(header: string): { +[string]: string } {
   const values = header.split(';').map(v => v.split('='));
 
-  const cookies = {};
+  const cookies: { [string]: string } = {};
   for (const [key, value] of values) {
     cookies[decodeURIComponent(key.trim())] = decodeURIComponent(value.trim());
   }
diff --git a/lib/utils/entity-helpers.js b/lib/utils/entity-helpers.js
--- a/lib/utils/entity-helpers.js
+++ b/lib/utils/entity-helpers.js
@@ -103,7 +103,7 @@
     options,
   );
   return React.useMemo(() => {
-    const obj = {};
+    const obj: { [string]: ResolvedThreadInfo } = {};
     for (const resolvedThreadInfo of resolvedThreadInfosArray) {
       obj[resolvedThreadInfo.id] = resolvedThreadInfo;
     }
diff --git a/lib/utils/promises.js b/lib/utils/promises.js
--- a/lib/utils/promises.js
+++ b/lib/utils/promises.js
@@ -2,7 +2,7 @@
 
 type Promisable<T> = Promise<T> | T;
 
-async function promiseAll<T: { [key: string]: Promisable<*> }>(
+async function promiseAll<T: { +[key: string]: Promisable<mixed> }>(
   input: T,
 ): Promise<$ObjMap<T, typeof $await>> {
   const promises = [];
@@ -13,7 +13,7 @@
     promises.push(promise);
   }
   const results = await Promise.all(promises);
-  const byName = {};
+  const byName: { [string]: mixed } = {};
   for (let i = 0; i < keys.length; i++) {
     const key = keys[i];
     byName[key] = results[i];
diff --git a/lib/utils/url-utils.js b/lib/utils/url-utils.js
--- a/lib/utils/url-utils.js
+++ b/lib/utils/url-utils.js
@@ -9,21 +9,23 @@
   pendingThreadIDRegex,
 } from './validation-utils.js';
 
-export type URLInfo = {
-  +year?: number,
-  +month?: number, // 1-indexed
-  +verify?: string,
-  +calendar?: boolean,
-  +chat?: boolean,
-  +thread?: string,
-  +settings?: 'account' | 'keyservers' | 'danger-zone',
-  +threadCreation?: boolean,
-  +selectedUserList?: $ReadOnlyArray<string>,
-  +inviteSecret?: string,
-  +qrCode?: boolean,
+type MutableURLInfo = {
+  year?: number,
+  month?: number, // 1-indexed
+  verify?: string,
+  calendar?: boolean,
+  chat?: boolean,
+  thread?: string,
+  settings?: 'account' | 'keyservers' | 'danger-zone',
+  threadCreation?: boolean,
+  selectedUserList?: $ReadOnlyArray<string>,
+  inviteSecret?: string,
+  qrCode?: boolean,
   ...
 };
 
+export type URLInfo = $ReadOnly<MutableURLInfo>;
+
 export const urlInfoValidator: TInterface<URLInfo> = tShape<URLInfo>({
   year: t.maybe(t.Number),
   month: t.maybe(t.Number),
@@ -78,7 +80,7 @@
   const inviteLinkMatches = inviteLinkRegex.exec(url);
   const qrCodeLoginMatches = qrCodeLoginRegex.exec(url);
 
-  const returnObj = {};
+  const returnObj: MutableURLInfo = {};
   if (yearMatches) {
     returnObj.year = parseInt(yearMatches[2], 10);
   }