Reinitialize redux after importing a backup

This commit is contained in:
Scott Nonnenberg 2024-08-27 00:26:21 +10:00 committed by GitHub
parent 19e0eb4444
commit abdef4847a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 437 additions and 264 deletions

View file

@ -48,11 +48,6 @@ import {
update as updateExpiringMessagesService, update as updateExpiringMessagesService,
} from './services/expiringMessagesDeletion'; } from './services/expiringMessagesDeletion';
import { tapToViewMessagesDeletionService } from './services/tapToViewMessagesDeletionService'; import { tapToViewMessagesDeletionService } from './services/tapToViewMessagesDeletionService';
import { getStoriesForRedux, loadStories } from './services/storyLoader';
import {
getDistributionListsForRedux,
loadDistributionLists,
} from './services/distributionListLoader';
import { senderCertificateService } from './services/senderCertificate'; import { senderCertificateService } from './services/senderCertificate';
import { GROUP_CREDENTIALS_KEY } from './services/groupCredentialFetcher'; import { GROUP_CREDENTIALS_KEY } from './services/groupCredentialFetcher';
import * as KeyboardLayout from './services/keyboardLayout'; import * as KeyboardLayout from './services/keyboardLayout';
@ -112,7 +107,6 @@ import { UpdateKeysListener } from './textsecure/UpdateKeysListener';
import { isDirectConversation } from './util/whatTypeOfConversation'; import { isDirectConversation } from './util/whatTypeOfConversation';
import { BackOff, FIBONACCI_TIMEOUTS } from './util/BackOff'; import { BackOff, FIBONACCI_TIMEOUTS } from './util/BackOff';
import { AppViewType } from './state/ducks/app'; import { AppViewType } from './state/ducks/app';
import type { BadgesStateType } from './state/ducks/badges';
import { areAnyCallsActiveOrRinging } from './state/selectors/calling'; import { areAnyCallsActiveOrRinging } from './state/selectors/calling';
import { badgeImageFileDownloader } from './badges/badgeImageFileDownloader'; import { badgeImageFileDownloader } from './badges/badgeImageFileDownloader';
import * as Deletes from './messageModifiers/Deletes'; import * as Deletes from './messageModifiers/Deletes';
@ -148,10 +142,8 @@ import {
import { isAciString } from './util/isAciString'; import { isAciString } from './util/isAciString';
import { normalizeAci } from './util/normalizeAci'; import { normalizeAci } from './util/normalizeAci';
import * as log from './logging/log'; import * as log from './logging/log';
import { loadRecentEmojis } from './util/loadRecentEmojis';
import { deleteAllLogs } from './util/deleteAllLogs'; import { deleteAllLogs } from './util/deleteAllLogs';
import { startInteractionMode } from './services/InteractionMode'; import { startInteractionMode } from './services/InteractionMode';
import type { MainWindowStatsType } from './windows/context';
import { ReactionSource } from './reactions/ReactionSource'; import { ReactionSource } from './reactions/ReactionSource';
import { singleProtoJobQueue } from './jobs/singleProtoJobQueue'; import { singleProtoJobQueue } from './jobs/singleProtoJobQueue';
import { import {
@ -178,26 +170,15 @@ import {
import { RetryPlaceholders } from './util/retryPlaceholders'; import { RetryPlaceholders } from './util/retryPlaceholders';
import { setBatchingStrategy } from './util/messageBatcher'; import { setBatchingStrategy } from './util/messageBatcher';
import { parseRemoteClientExpiration } from './util/parseRemoteClientExpiration'; import { parseRemoteClientExpiration } from './util/parseRemoteClientExpiration';
import { makeLookup } from './util/makeLookup';
import { addGlobalKeyboardShortcuts } from './services/addGlobalKeyboardShortcuts'; import { addGlobalKeyboardShortcuts } from './services/addGlobalKeyboardShortcuts';
import { createEventHandler } from './quill/signal-clipboard/util'; import { createEventHandler } from './quill/signal-clipboard/util';
import { onCallLogEventSync } from './util/onCallLogEventSync'; import { onCallLogEventSync } from './util/onCallLogEventSync';
import {
getCallsHistoryForRedux,
getCallsHistoryUnreadCountForRedux,
loadCallsHistory,
} from './services/callHistoryLoader';
import {
getCallLinksForRedux,
loadCallLinks,
} from './services/callLinksLoader';
import { backupsService } from './services/backups'; import { backupsService } from './services/backups';
import { import {
getCallIdFromEra, getCallIdFromEra,
updateLocalGroupCallHistoryTimestamp, updateLocalGroupCallHistoryTimestamp,
} from './util/callDisposition'; } from './util/callDisposition';
import { deriveStorageServiceKey } from './Crypto'; import { deriveStorageServiceKey } from './Crypto';
import { getThemeType } from './util/getThemeType';
import { AttachmentDownloadManager } from './jobs/AttachmentDownloadManager'; import { AttachmentDownloadManager } from './jobs/AttachmentDownloadManager';
import { onCallLinkUpdateSync } from './util/onCallLinkUpdateSync'; import { onCallLinkUpdateSync } from './util/onCallLinkUpdateSync';
import { CallMode } from './types/CallDisposition'; import { CallMode } from './types/CallDisposition';
@ -211,6 +192,7 @@ import { getConversationIdForLogging } from './util/idForLogging';
import { encryptConversationAttachments } from './util/encryptConversationAttachments'; import { encryptConversationAttachments } from './util/encryptConversationAttachments';
import { DataReader, DataWriter } from './sql/Client'; import { DataReader, DataWriter } from './sql/Client';
import { restoreRemoteConfigFromStorage } from './RemoteConfig'; import { restoreRemoteConfigFromStorage } from './RemoteConfig';
import { getParametersForRedux, loadAll } from './services/allLoaders';
export function isOverHourIntoPast(timestamp: number): boolean { export function isOverHourIntoPast(timestamp: number): boolean {
return isNumber(timestamp) && isOlderThan(timestamp, HOUR); return isNumber(timestamp) && isOlderThan(timestamp, HOUR);
@ -255,13 +237,6 @@ export async function startApp(): Promise<void> {
await initializeMessageCounter(); await initializeMessageCounter();
let initialBadgesState: BadgesStateType = { byId: {} };
async function loadInitialBadgesState(): Promise<void> {
initialBadgesState = {
byId: makeLookup(await DataReader.getAllBadges(), 'id'),
};
}
// Initialize WebAPI as early as possible // Initialize WebAPI as early as possible
let server: WebAPIType | undefined; let server: WebAPIType | undefined;
let messageReceiver: MessageReceiver | undefined; let messageReceiver: MessageReceiver | undefined;
@ -1110,21 +1085,6 @@ export async function startApp(): Promise<void> {
drop(window.Events.cleanupDownloads()); drop(window.Events.cleanupDownloads());
}, DAY); }, DAY);
let mainWindowStats = {
isMaximized: false,
isFullScreen: false,
};
let menuOptions = {
development: false,
devTools: false,
includeSetup: false,
isProduction: true,
platform: 'unknown',
};
let theme: ThemeType = window.systemTheme;
try { try {
// This needs to load before we prime the data because we expect // This needs to load before we prime the data because we expect
// ConversationController to be loaded and ready to use by then. // ConversationController to be loaded and ready to use by then.
@ -1132,23 +1092,8 @@ export async function startApp(): Promise<void> {
await Promise.all([ await Promise.all([
window.ConversationController.getOrCreateSignalConversation(), window.ConversationController.getOrCreateSignalConversation(),
Stickers.load(),
loadRecentEmojis(),
loadInitialBadgesState(),
loadStories(),
loadDistributionLists(),
loadCallsHistory(),
loadCallLinks(),
window.textsecure.storage.protocol.hydrateCaches(), window.textsecure.storage.protocol.hydrateCaches(),
(async () => { loadAll(),
mainWindowStats = await window.SignalContext.getMainWindowStats();
})(),
(async () => {
menuOptions = await window.SignalContext.getMenuOptions();
})(),
(async () => {
theme = await getThemeType();
})(),
]); ]);
await window.ConversationController.checkForConflicts(); await window.ConversationController.checkForConflicts();
} catch (error) { } catch (error) {
@ -1157,7 +1102,7 @@ export async function startApp(): Promise<void> {
Errors.toLogFormat(error) Errors.toLogFormat(error)
); );
} finally { } finally {
setupAppState({ mainWindowStats, menuOptions, theme }); setupAppState();
drop(start()); drop(start());
window.Signal.Services.initializeNetworkObserver( window.Signal.Services.initializeNetworkObserver(
window.reduxActions.network window.reduxActions.network
@ -1189,26 +1134,8 @@ export async function startApp(): Promise<void> {
log.info('Storage fetch'); log.info('Storage fetch');
drop(window.storage.fetch()); drop(window.storage.fetch());
function setupAppState({ function setupAppState() {
mainWindowStats, initializeRedux(getParametersForRedux());
menuOptions,
theme,
}: {
mainWindowStats: MainWindowStatsType;
menuOptions: MenuOptionsType;
theme: ThemeType;
}) {
initializeRedux({
callLinks: getCallLinksForRedux(),
callsHistory: getCallsHistoryForRedux(),
callsHistoryUnreadCount: getCallsHistoryUnreadCountForRedux(),
initialBadgesState,
mainWindowStats,
menuOptions,
stories: getStoriesForRedux(),
storyDistributionLists: getDistributionListsForRedux(),
theme,
});
// Here we set up a full redux store with initial state for our LeftPane Root // Here we set up a full redux store with initial state for our LeftPane Root
const convoCollection = window.getConversations(); const convoCollection = window.getConversations();

60
ts/services/allLoaders.ts Normal file
View file

@ -0,0 +1,60 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// loader services
import { getBadgesForRedux, loadBadges } from './badgeLoader';
import {
getCallsHistoryForRedux,
getCallsHistoryUnreadCountForRedux,
loadCallHistory,
} from './callHistoryLoader';
import { getCallLinksForRedux, loadCallLinks } from './callLinksLoader';
import {
getDistributionListsForRedux,
loadDistributionLists,
} from './distributionListLoader';
import { getStoriesForRedux, loadStories } from './storyLoader';
import { getUserDataForRedux, loadUserData } from './userLoader';
// old-style loaders
import {
getEmojiReducerState,
loadRecentEmojis,
} from '../util/loadRecentEmojis';
import {
load as loadStickers,
getInitialState as getStickersReduxState,
} from '../types/Stickers';
import type { ReduxInitData } from '../state/initializeRedux';
export async function loadAll(): Promise<void> {
await Promise.all([
loadBadges(),
loadCallHistory(),
loadCallLinks(),
loadDistributionLists(),
loadRecentEmojis(),
loadStickers(),
loadStories(),
loadUserData(),
]);
}
export function getParametersForRedux(): ReduxInitData {
const { mainWindowStats, menuOptions, theme } = getUserDataForRedux();
return {
badgesState: getBadgesForRedux(),
callHistory: getCallsHistoryForRedux(),
callHistoryUnreadCount: getCallsHistoryUnreadCountForRedux(),
callLinks: getCallLinksForRedux(),
mainWindowStats,
menuOptions,
recentEmoji: getEmojiReducerState(),
stickers: getStickersReduxState(),
stories: getStoriesForRedux(),
storyDistributionLists: getDistributionListsForRedux(),
theme,
};
}

View file

@ -88,7 +88,6 @@ import { canBeSynced as canPreferredReactionEmojiBeSynced } from '../../reaction
import { SendStatus } from '../../messages/MessageSendState'; import { SendStatus } from '../../messages/MessageSendState';
import { BACKUP_VERSION } from './constants'; import { BACKUP_VERSION } from './constants';
import { getMessageIdForLogging } from '../../util/idForLogging'; import { getMessageIdForLogging } from '../../util/idForLogging';
import { getCallsHistoryForRedux } from '../callHistoryLoader';
import { makeLookup } from '../../util/makeLookup'; import { makeLookup } from '../../util/makeLookup';
import type { import type {
CallHistoryDetails, CallHistoryDetails,
@ -470,7 +469,7 @@ export class BackupExportStream extends Readable {
let cursor: PageMessagesCursorType | undefined; let cursor: PageMessagesCursorType | undefined;
const callHistory = getCallsHistoryForRedux(); const callHistory = await DataReader.getAllCallHistory();
const callHistoryByCallId = makeLookup(callHistory, 'callId'); const callHistoryByCallId = makeLookup(callHistory, 'callId');
const me = window.ConversationController.getOurConversationOrThrow(); const me = window.ConversationController.getOurConversationOrThrow();

View file

@ -34,6 +34,8 @@ import { getKeyMaterial } from './crypto';
import { BackupCredentials } from './credentials'; import { BackupCredentials } from './credentials';
import { BackupAPI } from './api'; import { BackupAPI } from './api';
import { validateBackup } from './validator'; import { validateBackup } from './validator';
import { reinitializeRedux } from '../../state/reinitializeRedux';
import { getParametersForRedux, loadAll } from '../allLoaders';
const IV_LENGTH = 16; const IV_LENGTH = 16;
@ -192,14 +194,27 @@ export class BackupsService {
'importBackup: Bad MAC, second pass' 'importBackup: Bad MAC, second pass'
); );
await this.resetStateAfterImport();
log.info('importBackup: finished...'); log.info('importBackup: finished...');
} catch (error) { } catch (error) {
log.info(`importBackup: failed, error: ${Errors.toLogFormat(error)}`); log.info(`importBackup: failed, error: ${Errors.toLogFormat(error)}`);
throw error; throw error;
} finally { } finally {
this.isRunning = false; this.isRunning = false;
if (window.SignalCI) {
window.SignalCI.handleEvent('backupImportComplete', null);
} }
} }
}
public async resetStateAfterImport(): Promise<void> {
window.ConversationController.reset();
await window.ConversationController.load();
await loadAll();
reinitializeRedux(getParametersForRedux());
}
public async fetchAndSaveBackupCdnObjectMetadata(): Promise<void> { public async fetchAndSaveBackupCdnObjectMetadata(): Promise<void> {
log.info('fetchAndSaveBackupCdnObjectMetadata: clearing existing metadata'); log.info('fetchAndSaveBackupCdnObjectMetadata: clearing existing metadata');

View file

@ -0,0 +1,22 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { DataReader } from '../sql/Client';
import { strictAssert } from '../util/assert';
import { makeLookup } from '../util/makeLookup';
import type { BadgeType } from '../badges/types';
import type { BadgesStateType } from '../state/ducks/badges';
let badges: Array<BadgeType> | undefined;
export async function loadBadges(): Promise<void> {
badges = await DataReader.getAllBadges();
}
export function getBadgesForRedux(): BadgesStateType {
strictAssert(badges != null, 'badges have not been loaded');
return {
byId: makeLookup(badges, 'id'),
};
}

View file

@ -8,7 +8,7 @@ import { strictAssert } from '../util/assert';
let callsHistoryData: ReadonlyArray<CallHistoryDetails>; let callsHistoryData: ReadonlyArray<CallHistoryDetails>;
let callsHistoryUnreadCount: number; let callsHistoryUnreadCount: number;
export async function loadCallsHistory(): Promise<void> { export async function loadCallHistory(): Promise<void> {
await DataWriter.cleanupCallHistoryMessages(); await DataWriter.cleanupCallHistoryMessages();
callsHistoryData = await DataReader.getAllCallHistory(); callsHistoryData = await DataReader.getAllCallHistory();
callsHistoryUnreadCount = await DataReader.getCallHistoryUnreadCount(); callsHistoryUnreadCount = await DataReader.getCallHistoryUnreadCount();

39
ts/services/userLoader.ts Normal file
View file

@ -0,0 +1,39 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { strictAssert } from '../util/assert';
import { getThemeType } from '../util/getThemeType';
import type { MenuOptionsType } from '../types/menu';
import type { MainWindowStatsType } from '../windows/context';
import type { ThemeType } from '../types/Util';
let mainWindowStats: MainWindowStatsType | undefined;
let menuOptions: MenuOptionsType | undefined;
let theme: ThemeType | undefined;
export async function loadUserData(): Promise<void> {
await Promise.all([
(async () => {
mainWindowStats = await window.SignalContext.getMainWindowStats();
})(),
(async () => {
menuOptions = await window.SignalContext.getMenuOptions();
})(),
(async () => {
theme = await getThemeType();
})(),
]);
}
export function getUserDataForRedux(): {
mainWindowStats: MainWindowStatsType;
menuOptions: MenuOptionsType;
theme: ThemeType;
} {
strictAssert(
mainWindowStats != null && menuOptions != null && theme != null,
'user data has not been loaded'
);
return { mainWindowStats, menuOptions, theme };
}

View file

@ -26,7 +26,7 @@ import {
import { import {
getCallsHistoryForRedux, getCallsHistoryForRedux,
getCallsHistoryUnreadCountForRedux, getCallsHistoryUnreadCountForRedux,
loadCallsHistory, loadCallHistory,
} from '../../services/callHistoryLoader'; } from '../../services/callHistoryLoader';
import { makeLookup } from '../../util/makeLookup'; import { makeLookup } from '../../util/makeLookup';
@ -217,7 +217,7 @@ export function reloadCallHistory(): ThunkAction<
> { > {
return async dispatch => { return async dispatch => {
try { try {
await loadCallsHistory(); await loadCallHistory();
const callsHistory = getCallsHistoryForRedux(); const callsHistory = getCallsHistoryForRedux();
const callsHistoryUnreadCount = getCallsHistoryUnreadCountForRedux(); const callsHistoryUnreadCount = getCallsHistoryUnreadCountForRedux();
dispatch({ dispatch({
@ -234,6 +234,7 @@ export const actions = {
addCallHistory, addCallHistory,
removeCallHistory, removeCallHistory,
resetCallHistory, resetCallHistory,
reloadCallHistory,
clearAllCallHistory, clearAllCallHistory,
updateCallHistoryUnreadCount, updateCallHistoryUnreadCount,
markCallHistoryRead, markCallHistoryRead,

View file

@ -59,7 +59,7 @@ function useEmoji(payload: string): UseEmojiAction {
// Reducer // Reducer
function getEmptyState(): EmojisStateType { export function getEmptyState(): EmojisStateType {
return { return {
recents: [], recents: [],
}; };

View file

@ -1,75 +1,175 @@
// Copyright 2022 Signal Messenger, LLC // Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { getEmptyState as accounts } from './ducks/accounts'; import { getEmptyState as accountsEmptyState } from './ducks/accounts';
import { getEmptyState as app } from './ducks/app'; import { getEmptyState as appEmptyState } from './ducks/app';
import { getEmptyState as audioPlayer } from './ducks/audioPlayer'; import { getEmptyState as audioPlayerEmptyState } from './ducks/audioPlayer';
import { getEmptyState as audioRecorder } from './ducks/audioRecorder'; import { getEmptyState as audioRecorderEmptyState } from './ducks/audioRecorder';
import { getEmptyState as callHistory } from './ducks/callHistory'; import { getEmptyState as badgesEmptyState } from './ducks/badges';
import { getEmptyState as calling } from './ducks/calling'; import { getEmptyState as callHistoryEmptyState } from './ducks/callHistory';
import { getEmptyState as composer } from './ducks/composer'; import { getEmptyState as callingEmptyState } from './ducks/calling';
import { getEmptyState as conversations } from './ducks/conversations'; import { getEmptyState as composerEmptyState } from './ducks/composer';
import { getEmptyState as crashReports } from './ducks/crashReports'; import { getEmptyState as conversationsEmptyState } from './ducks/conversations';
import { getEmptyState as expiration } from './ducks/expiration'; import { getEmptyState as crashReportsEmptyState } from './ducks/crashReports';
import { getEmptyState as globalModals } from './ducks/globalModals'; import { getEmptyState as emojiEmptyState } from './ducks/emojis';
import { getEmptyState as inbox } from './ducks/inbox'; import { getEmptyState as itemsEmptyState } from './ducks/items';
import { getEmptyState as lightbox } from './ducks/lightbox'; import { getEmptyState as stickersEmptyState } from './ducks/stickers';
import { getEmptyState as linkPreviews } from './ducks/linkPreviews'; import { getEmptyState as expirationEmptyState } from './ducks/expiration';
import { getEmptyState as mediaGallery } from './ducks/mediaGallery'; import { getEmptyState as globalModalsEmptyState } from './ducks/globalModals';
import { getEmptyState as nav } from './ducks/nav'; import { getEmptyState as inboxEmptyState } from './ducks/inbox';
import { getEmptyState as network } from './ducks/network'; import { getEmptyState as lightboxEmptyState } from './ducks/lightbox';
import { getEmptyState as preferredReactions } from './ducks/preferredReactions'; import { getEmptyState as linkPreviewsEmptyState } from './ducks/linkPreviews';
import { getEmptyState as safetyNumber } from './ducks/safetyNumber'; import { getEmptyState as mediaGalleryEmptyState } from './ducks/mediaGallery';
import { getEmptyState as search } from './ducks/search'; import { getEmptyState as navEmptyState } from './ducks/nav';
import { getEmptyState as getStoriesEmptyState } from './ducks/stories'; import { getEmptyState as networkEmptyState } from './ducks/network';
import { getEmptyState as getStoryDistributionListsEmptyState } from './ducks/storyDistributionLists'; import { getEmptyState as preferredReactionsEmptyState } from './ducks/preferredReactions';
import { getEmptyState as getToastEmptyState } from './ducks/toast'; import { getEmptyState as safetyNumberEmptyState } from './ducks/safetyNumber';
import { getEmptyState as updates } from './ducks/updates'; import { getEmptyState as searchEmptyState } from './ducks/search';
import { getEmptyState as user } from './ducks/user'; import { getEmptyState as storiesEmptyState } from './ducks/stories';
import { getEmptyState as username } from './ducks/username'; import { getEmptyState as storyDistributionListsEmptyState } from './ducks/storyDistributionLists';
import { getEmptyState as toastEmptyState } from './ducks/toast';
import { getEmptyState as updatesEmptyState } from './ducks/updates';
import { getEmptyState as userEmptyState } from './ducks/user';
import { getEmptyState as usernameEmptyState } from './ducks/username';
import type { StateType } from './reducer';
import type { BadgesStateType } from './ducks/badges';
import type { MainWindowStatsType } from '../windows/context';
import type { MenuOptionsType } from '../types/menu';
import type { StoryDataType } from './ducks/stories';
import type { StoryDistributionListDataType } from './ducks/storyDistributionLists';
import OS from '../util/os/osMain'; import OS from '../util/os/osMain';
import { getEmojiReducerState as emojis } from '../util/loadRecentEmojis';
import { getInitialState as stickers } from '../types/Stickers';
import { getInteractionMode } from '../services/InteractionMode'; import { getInteractionMode } from '../services/InteractionMode';
import { makeLookup } from '../util/makeLookup'; import { makeLookup } from '../util/makeLookup';
import type { CallHistoryDetails } from '../types/CallDisposition';
import type { ThemeType } from '../types/Util';
import type { CallLinkType } from '../types/CallLink';
export function getInitialState({ import type { StateType } from './reducer';
badges, import type { MainWindowStatsType } from '../windows/context';
import type { ConversationsStateType } from './ducks/conversations';
import type { MenuOptionsType } from '../types/menu';
import type {
StoryDistributionListDataType,
StoryDistributionListStateType,
} from './ducks/storyDistributionLists';
import type { ThemeType } from '../types/Util';
import type { UserStateType } from './ducks/user';
import type { ReduxInitData } from './initializeRedux';
export function getInitialState(
{
badgesState,
callLinks, callLinks,
callsHistory, callHistory: calls,
callsHistoryUnreadCount, callHistoryUnreadCount,
mainWindowStats,
menuOptions,
recentEmoji,
stickers,
stories, stories,
storyDistributionLists, storyDistributionLists,
theme,
}: ReduxInitData,
existingState?: StateType
): StateType {
const items = window.storage.getItemsState();
const baseState: StateType = existingState ?? getEmptyState();
return {
...baseState,
badges: badgesState,
callHistory: {
...callHistoryEmptyState(),
callHistoryByCallId: makeLookup(calls, 'callId'),
unreadCount: callHistoryUnreadCount,
},
calling: {
...callingEmptyState(),
callLinks: makeLookup(callLinks, 'roomId'),
},
emojis: recentEmoji,
items,
stickers,
stories: {
...storiesEmptyState(),
stories,
},
storyDistributionLists: generateStoryDistributionListState(
storyDistributionLists
),
user: generateUserState({
mainWindowStats, mainWindowStats,
menuOptions, menuOptions,
theme, theme,
}: { }),
badges: BadgesStateType; };
callLinks: ReadonlyArray<CallLinkType>; }
callsHistory: ReadonlyArray<CallHistoryDetails>;
callsHistoryUnreadCount: number;
stories: Array<StoryDataType>;
storyDistributionLists: Array<StoryDistributionListDataType>;
mainWindowStats: MainWindowStatsType;
menuOptions: MenuOptionsType;
theme: ThemeType;
}): StateType {
const items = window.storage.getItemsState();
export function generateConversationsState(): ConversationsStateType {
const convoCollection = window.getConversations(); const convoCollection = window.getConversations();
const formattedConversations = convoCollection.map(conversation => const formattedConversations = convoCollection.map(conversation =>
conversation.format() conversation.format()
); );
return {
...conversationsEmptyState(),
conversationLookup: makeLookup(formattedConversations, 'id'),
conversationsByE164: makeLookup(formattedConversations, 'e164'),
conversationsByServiceId: {
...makeLookup(formattedConversations, 'serviceId'),
...makeLookup(formattedConversations, 'pni'),
},
conversationsByGroupId: makeLookup(formattedConversations, 'groupId'),
conversationsByUsername: makeLookup(formattedConversations, 'username'),
};
}
function getEmptyState(): StateType {
return {
accounts: accountsEmptyState(),
app: appEmptyState(),
audioPlayer: audioPlayerEmptyState(),
audioRecorder: audioRecorderEmptyState(),
badges: badgesEmptyState(),
callHistory: callHistoryEmptyState(),
calling: callingEmptyState(),
composer: composerEmptyState(),
conversations: generateConversationsState(),
crashReports: crashReportsEmptyState(),
emojis: emojiEmptyState(),
expiration: expirationEmptyState(),
globalModals: globalModalsEmptyState(),
inbox: inboxEmptyState(),
items: itemsEmptyState(),
lightbox: lightboxEmptyState(),
linkPreviews: linkPreviewsEmptyState(),
mediaGallery: mediaGalleryEmptyState(),
nav: navEmptyState(),
network: networkEmptyState(),
preferredReactions: preferredReactionsEmptyState(),
safetyNumber: safetyNumberEmptyState(),
search: searchEmptyState(),
stickers: stickersEmptyState(),
stories: storiesEmptyState(),
storyDistributionLists: storyDistributionListsEmptyState(),
toast: toastEmptyState(),
updates: updatesEmptyState(),
user: userEmptyState(),
username: usernameEmptyState(),
};
}
export function generateStoryDistributionListState(
storyDistributionLists: ReadonlyArray<StoryDistributionListDataType>
): StoryDistributionListStateType {
return {
...storyDistributionListsEmptyState(),
distributionLists: storyDistributionLists || [],
};
}
export function generateUserState({
mainWindowStats,
menuOptions,
theme,
}: {
mainWindowStats: MainWindowStatsType;
menuOptions: MenuOptionsType;
theme: ThemeType;
}): UserStateType {
const ourNumber = window.textsecure.storage.user.getNumber(); const ourNumber = window.textsecure.storage.user.getNumber();
const ourAci = window.textsecure.storage.user.getAci(); const ourAci = window.textsecure.storage.user.getAci();
const ourPni = window.textsecure.storage.user.getPni(); const ourPni = window.textsecure.storage.user.getPni();
@ -88,59 +188,7 @@ export function getInitialState({
} }
return { return {
accounts: accounts(), ...userEmptyState(),
app: app(),
audioPlayer: audioPlayer(),
audioRecorder: audioRecorder(),
badges,
callHistory: {
...callHistory(),
callHistoryByCallId: makeLookup(callsHistory, 'callId'),
unreadCount: callsHistoryUnreadCount,
},
calling: {
...calling(),
callLinks: makeLookup(callLinks, 'roomId'),
},
composer: composer(),
conversations: {
...conversations(),
conversationLookup: makeLookup(formattedConversations, 'id'),
conversationsByE164: makeLookup(formattedConversations, 'e164'),
conversationsByServiceId: {
...makeLookup(formattedConversations, 'serviceId'),
...makeLookup(formattedConversations, 'pni'),
},
conversationsByGroupId: makeLookup(formattedConversations, 'groupId'),
conversationsByUsername: makeLookup(formattedConversations, 'username'),
},
crashReports: crashReports(),
emojis: emojis(),
expiration: expiration(),
globalModals: globalModals(),
inbox: inbox(),
items,
lightbox: lightbox(),
linkPreviews: linkPreviews(),
mediaGallery: mediaGallery(),
nav: nav(),
network: network(),
preferredReactions: preferredReactions(),
safetyNumber: safetyNumber(),
search: search(),
stickers: stickers(),
stories: {
...getStoriesEmptyState(),
stories,
},
storyDistributionLists: {
...getStoryDistributionListsEmptyState(),
distributionLists: storyDistributionLists || [],
},
toast: getToastEmptyState(),
updates: updates(),
user: {
...user(),
attachmentsPath: window.BasePaths.attachments, attachmentsPath: window.BasePaths.attachments,
i18n: window.i18n, i18n: window.i18n,
interactionMode: getInteractionMode(), interactionMode: getInteractionMode(),
@ -160,7 +208,5 @@ export function getInitialState({
tempPath: window.BasePaths.temp, tempPath: window.BasePaths.temp,
theme, theme,
version: window.getVersion(), version: window.getVersion(),
},
username: username(),
}; };
} }

View file

@ -2,50 +2,37 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { actionCreators } from './actions';
import { createStore } from './createStore';
import { getInitialState } from './getInitialState';
import type { BadgesStateType } from './ducks/badges'; import type { BadgesStateType } from './ducks/badges';
import type { CallHistoryDetails } from '../types/CallDisposition'; import type { CallHistoryDetails } from '../types/CallDisposition';
import type { MainWindowStatsType } from '../windows/context'; import type { MainWindowStatsType } from '../windows/context';
import type { MenuOptionsType } from '../types/menu'; import type { MenuOptionsType } from '../types/menu';
import type { StoryDataType } from './ducks/stories'; import type { StoryDataType } from './ducks/stories';
import type { StoryDistributionListDataType } from './ducks/storyDistributionLists'; import type { StoryDistributionListDataType } from './ducks/storyDistributionLists';
import { actionCreators } from './actions';
import { createStore } from './createStore';
import { getInitialState } from './getInitialState';
import type { ThemeType } from '../types/Util'; import type { ThemeType } from '../types/Util';
import type { CallLinkType } from '../types/CallLink'; import type { CallLinkType } from '../types/CallLink';
import type { RecentEmojiObjectType } from '../util/loadRecentEmojis';
import type { StickersStateType } from './ducks/stickers';
export function initializeRedux({ export type ReduxInitData = {
callLinks, badgesState: BadgesStateType;
callsHistory, callHistory: ReadonlyArray<CallHistoryDetails>;
callsHistoryUnreadCount, callHistoryUnreadCount: number;
initialBadgesState,
mainWindowStats,
menuOptions,
stories,
storyDistributionLists,
theme,
}: {
callLinks: ReadonlyArray<CallLinkType>; callLinks: ReadonlyArray<CallLinkType>;
callsHistory: ReadonlyArray<CallHistoryDetails>;
callsHistoryUnreadCount: number;
initialBadgesState: BadgesStateType;
mainWindowStats: MainWindowStatsType; mainWindowStats: MainWindowStatsType;
menuOptions: MenuOptionsType; menuOptions: MenuOptionsType;
recentEmoji: RecentEmojiObjectType;
stickers: StickersStateType;
stories: Array<StoryDataType>; stories: Array<StoryDataType>;
storyDistributionLists: Array<StoryDistributionListDataType>; storyDistributionLists: Array<StoryDistributionListDataType>;
theme: ThemeType; theme: ThemeType;
}): void { };
const initialState = getInitialState({
badges: initialBadgesState, export function initializeRedux(data: ReduxInitData): void {
callLinks, const initialState = getInitialState(data);
callsHistory,
callsHistoryUnreadCount,
mainWindowStats,
menuOptions,
stories,
storyDistributionLists,
theme,
});
const store = createStore(initialState); const store = createStore(initialState);
window.reduxStore = store; window.reduxStore = store;

View file

@ -0,0 +1,57 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { AnyAction } from 'redux';
import * as log from '../logging/log';
import { getInitialState } from './getInitialState';
import { reducer as normalReducer } from './reducer';
import type { StateType } from './reducer';
import type { ReduxInitData } from './initializeRedux';
const REPLACE_STATE = 'resetReducer/REPLACE';
export function reinitializeRedux(options: ReduxInitData): void {
const logId = 'initializeRedux';
const existingState = window.reduxStore.getState();
const newInitialState = getInitialState(options, existingState);
const resetReducer = (
state: StateType | undefined,
action: AnyAction
): StateType => {
if (state == null) {
log.info(
`${logId}/resetReducer: Got null incoming state, returning newInitialState`
);
return newInitialState;
}
const { type } = action;
if (type === REPLACE_STATE) {
log.info(
`${logId}/resetReducer: Got REPLACE_STATE action, returning newInitialState`
);
return newInitialState;
}
log.info(
`${logId}/resetReducer: Got action with type ${type}, returning original state`
);
return state;
};
log.info(`${logId}: installing resetReducer`);
window.reduxStore.replaceReducer(resetReducer);
log.info(`${logId}: dispatching REPLACE_STATE event`);
window.reduxStore.dispatch({
type: REPLACE_STATE,
});
log.info(`${logId}: restoring original reducer`);
window.reduxStore.replaceReducer(normalReducer);
log.info(`${logId}: complete!`);
}

View file

@ -14,7 +14,6 @@ import { DataWriter } from '../../sql/Client';
import { type AciString, generateAci } from '../../types/ServiceId'; import { type AciString, generateAci } from '../../types/ServiceId';
import { ReadStatus } from '../../messages/MessageReadStatus'; import { ReadStatus } from '../../messages/MessageReadStatus';
import { SeenStatus } from '../../MessageSeenStatus'; import { SeenStatus } from '../../MessageSeenStatus';
import { loadCallsHistory } from '../../services/callHistoryLoader';
import { setupBasics, asymmetricRoundtripHarness } from './helpers'; import { setupBasics, asymmetricRoundtripHarness } from './helpers';
import { import {
AUDIO_MP3, AUDIO_MP3,
@ -31,6 +30,7 @@ import { isVoiceMessage, type AttachmentType } from '../../types/Attachment';
import { strictAssert } from '../../util/assert'; import { strictAssert } from '../../util/assert';
import { SignalService } from '../../protobuf'; import { SignalService } from '../../protobuf';
import { getRandomBytes } from '../../Crypto'; import { getRandomBytes } from '../../Crypto';
import { loadAll } from '../../services/allLoaders';
const CONTACT_A = generateAci(); const CONTACT_A = generateAci();
@ -51,7 +51,7 @@ describe('backup/attachments', () => {
{ systemGivenName: 'CONTACT_A' } { systemGivenName: 'CONTACT_A' }
); );
await loadCallsHistory(); await loadAll();
sandbox = sinon.createSandbox(); sandbox = sinon.createSandbox();
const getAbsoluteAttachmentPath = sandbox.stub( const getAbsoluteAttachmentPath = sandbox.stub(

View file

@ -12,7 +12,6 @@ import type { MessageAttributesType } from '../../model-types';
import type { GroupV2ChangeType } from '../../groups'; import type { GroupV2ChangeType } from '../../groups';
import { getRandomBytes } from '../../Crypto'; import { getRandomBytes } from '../../Crypto';
import * as Bytes from '../../Bytes'; import * as Bytes from '../../Bytes';
import { loadCallsHistory } from '../../services/callHistoryLoader';
import { strictAssert } from '../../util/assert'; import { strictAssert } from '../../util/assert';
import { DurationInSeconds } from '../../util/durations'; import { DurationInSeconds } from '../../util/durations';
import { import {
@ -24,6 +23,7 @@ import {
} from './helpers'; } from './helpers';
import { ReadStatus } from '../../messages/MessageReadStatus'; import { ReadStatus } from '../../messages/MessageReadStatus';
import { SeenStatus } from '../../MessageSeenStatus'; import { SeenStatus } from '../../MessageSeenStatus';
import { loadAll } from '../../services/allLoaders';
// Note: this should be kept up to date with GroupV2Change.stories.tsx, to // Note: this should be kept up to date with GroupV2Change.stories.tsx, to
// maintain the comprehensive set of GroupV2 notifications we need to handle // maintain the comprehensive set of GroupV2 notifications we need to handle
@ -114,7 +114,7 @@ describe('backup/groupv2/notifications', () => {
name: 'Rock Enthusiasts', name: 'Rock Enthusiasts',
}); });
await loadCallsHistory(); await loadAll();
}); });
afterEach(async () => { afterEach(async () => {
await DataWriter.removeAll(); await DataWriter.removeAll();

View file

@ -13,7 +13,6 @@ import * as Bytes from '../../Bytes';
import { generateAci } from '../../types/ServiceId'; import { generateAci } from '../../types/ServiceId';
import { ReadStatus } from '../../messages/MessageReadStatus'; import { ReadStatus } from '../../messages/MessageReadStatus';
import { SeenStatus } from '../../MessageSeenStatus'; import { SeenStatus } from '../../MessageSeenStatus';
import { loadCallsHistory } from '../../services/callHistoryLoader';
import { ID_V1_LENGTH } from '../../groups'; import { ID_V1_LENGTH } from '../../groups';
import { DurationInSeconds, WEEK } from '../../util/durations'; import { DurationInSeconds, WEEK } from '../../util/durations';
import { import {
@ -22,6 +21,7 @@ import {
symmetricRoundtripHarness, symmetricRoundtripHarness,
OUR_ACI, OUR_ACI,
} from './helpers'; } from './helpers';
import { loadAll } from '../../services/allLoaders';
const CONTACT_A = generateAci(); const CONTACT_A = generateAci();
const CONTACT_B = generateAci(); const CONTACT_B = generateAci();
@ -67,7 +67,7 @@ describe('backup/bubble messages', () => {
} }
); );
await loadCallsHistory(); await loadAll();
}); });
it('roundtrips incoming edited message', async () => { it('roundtrips incoming edited message', async () => {

View file

@ -14,7 +14,6 @@ import * as Bytes from '../../Bytes';
import { getRandomBytes } from '../../Crypto'; import { getRandomBytes } from '../../Crypto';
import { DataReader, DataWriter } from '../../sql/Client'; import { DataReader, DataWriter } from '../../sql/Client';
import { generateAci } from '../../types/ServiceId'; import { generateAci } from '../../types/ServiceId';
import { loadCallsHistory } from '../../services/callHistoryLoader';
import { setupBasics, symmetricRoundtripHarness } from './helpers'; import { setupBasics, symmetricRoundtripHarness } from './helpers';
import { import {
AdhocCallStatus, AdhocCallStatus,
@ -30,6 +29,7 @@ import { fromAdminKeyBytes } from '../../util/callLinks';
import { ReadStatus } from '../../messages/MessageReadStatus'; import { ReadStatus } from '../../messages/MessageReadStatus';
import { SeenStatus } from '../../MessageSeenStatus'; import { SeenStatus } from '../../MessageSeenStatus';
import { deriveGroupID, deriveGroupSecretParams } from '../../util/zkgroup'; import { deriveGroupID, deriveGroupSecretParams } from '../../util/zkgroup';
import { loadAll } from '../../services/allLoaders';
const CONTACT_A = generateAci(); const CONTACT_A = generateAci();
const GROUP_MASTER_KEY = getRandomBytes(32); const GROUP_MASTER_KEY = getRandomBytes(32);
@ -78,7 +78,7 @@ describe('backup/calling', () => {
await DataWriter.insertCallLink(callLink); await DataWriter.insertCallLink(callLink);
await loadCallsHistory(); await loadAll();
}); });
after(async () => { after(async () => {
await DataWriter.removeAll(); await DataWriter.removeAll();
@ -99,7 +99,7 @@ describe('backup/calling', () => {
timestamp: now, timestamp: now,
}; };
await DataWriter.saveCallHistory(callHistory); await DataWriter.saveCallHistory(callHistory);
await loadCallsHistory(); await loadAll();
const messageUnseen: MessageAttributesType = { const messageUnseen: MessageAttributesType = {
id: generateGuid(), id: generateGuid(),
@ -146,7 +146,7 @@ describe('backup/calling', () => {
timestamp: now, timestamp: now,
}; };
await DataWriter.saveCallHistory(callHistory); await DataWriter.saveCallHistory(callHistory);
await loadCallsHistory(); await loadAll();
const messageUnseen: MessageAttributesType = { const messageUnseen: MessageAttributesType = {
id: generateGuid(), id: generateGuid(),
@ -231,7 +231,7 @@ describe('backup/calling', () => {
timestamp: now, timestamp: now,
}; };
await DataWriter.saveCallHistory(callHistory); await DataWriter.saveCallHistory(callHistory);
await loadCallsHistory(); await loadAll();
await symmetricRoundtripHarness([]); await symmetricRoundtripHarness([]);
@ -255,7 +255,7 @@ describe('backup/calling', () => {
timestamp: now, timestamp: now,
}; };
await DataWriter.saveCallHistory(callHistory); await DataWriter.saveCallHistory(callHistory);
await loadCallsHistory(); await loadAll();
await symmetricRoundtripHarness([]); await symmetricRoundtripHarness([]);

View file

@ -18,13 +18,13 @@ import { MessageRequestResponseEvent } from '../../types/MessageRequestResponseE
import { DurationInSeconds } from '../../util/durations'; import { DurationInSeconds } from '../../util/durations';
import { ReadStatus } from '../../messages/MessageReadStatus'; import { ReadStatus } from '../../messages/MessageReadStatus';
import { SeenStatus } from '../../MessageSeenStatus'; import { SeenStatus } from '../../MessageSeenStatus';
import { loadCallsHistory } from '../../services/callHistoryLoader';
import { import {
setupBasics, setupBasics,
asymmetricRoundtripHarness, asymmetricRoundtripHarness,
symmetricRoundtripHarness, symmetricRoundtripHarness,
OUR_ACI, OUR_ACI,
} from './helpers'; } from './helpers';
import { loadAll } from '../../services/allLoaders';
const CONTACT_A = generateAci(); const CONTACT_A = generateAci();
const GROUP_ID = Bytes.toBase64(getRandomBytes(32)); const GROUP_ID = Bytes.toBase64(getRandomBytes(32));
@ -56,7 +56,7 @@ describe('backup/non-bubble messages', () => {
} }
); );
await loadCallsHistory(); await loadAll();
}); });
it('roundtrips END_SESSION simple update', async () => { it('roundtrips END_SESSION simple update', async () => {

View file

@ -344,6 +344,11 @@ export class Bootstrap {
} }
} }
if (extraConfig?.ciBackupPath) {
debug('waiting for backup import to complete');
await app.waitForBackupImportComplete();
}
await this.phone.waitForSync(this.desktop); await this.phone.waitForSync(this.desktop);
this.phone.resetSyncState(this.desktop); this.phone.resetSyncState(this.desktop);
@ -512,7 +517,9 @@ export class Bootstrap {
return; return;
} }
debug('screenshot difference', numPixels); debug(
`screenshot difference for ${name}: ${numPixels}/${width * height}`
);
const outDir = await this.getArtifactsDir(test?.fullTitle()); const outDir = await this.getArtifactsDir(test?.fullTitle());
if (outDir != null) { if (outDir != null) {

View file

@ -114,6 +114,10 @@ export class App extends EventEmitter {
return this.waitForEvent('app-loaded'); return this.waitForEvent('app-loaded');
} }
public async waitForBackupImportComplete(): Promise<void> {
return this.waitForEvent('backupImportComplete');
}
public async waitForMessageSend(): Promise<MessageSendInfoType> { public async waitForMessageSend(): Promise<MessageSendInfoType> {
return this.waitForEvent('message:send-complete'); return this.waitForEvent('message:send-complete');
} }

View file

@ -4,7 +4,7 @@
import { take } from 'lodash'; import { take } from 'lodash';
import { DataReader } from '../sql/Client'; import { DataReader } from '../sql/Client';
type RecentEmojiObjectType = { export type RecentEmojiObjectType = {
recents: Array<string>; recents: Array<string>;
}; };

View file

@ -99,10 +99,10 @@ window.testUtilities = {
await Stickers.load(); await Stickers.load();
initializeRedux({ initializeRedux({
badgesState: { byId: {} },
callLinks: [], callLinks: [],
callsHistory: [], callHistory: [],
callsHistoryUnreadCount: 0, callHistoryUnreadCount: 0,
initialBadgesState: { byId: {} },
mainWindowStats: { mainWindowStats: {
isFullScreen: false, isFullScreen: false,
isMaximized: false, isMaximized: false,
@ -114,8 +114,17 @@ window.testUtilities = {
isProduction: false, isProduction: false,
platform: 'test', platform: 'test',
}, },
recentEmoji: {
recents: [],
},
stories: [], stories: [],
storyDistributionLists: [], storyDistributionLists: [],
stickers: {
installedPack: null,
packs: {},
recentStickers: [],
blessedPacks: {},
},
theme: ThemeType.dark, theme: ThemeType.dark,
}); });
}, },