mattermost-mobile-upstream/app/init/app.ts
Nick Misasi f663d0f520
perf: skip Android Keystore listing on cold start (#10056)
* perf: skip Android Keystore listing on cold start

Open the app DB first and decrypt credentials only for known server URLs so logged-in launches no longer enumerate every generic-password service.

Co-authored-by: Cursor <cursoragent@cursor.com>

* perf: load server credentials in one parallel pass

Drop the extra preauth Set and second keystore probe so cold start decrypts each known URL once, and read active servers from the existing query.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: retry failed startup and cache credentials only after Keychain writes

Reset base initialization so a rejected launch can retry, and keep the in-memory credential cache aligned with successful secure-storage writes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: drop mock-harness init tests and unused session cache

The initialize() retry test only asserted mock wiring. Remove leftover
hasCachedCredentials/session_cache APIs from closed stack floors, and
keep credential tests that lock listing-skip and cache behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: drop leftover launch profiler instrumentation

Cold-start timing marks were debug logInfo breadcrumbs, not part of the Keystore skip.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: assert empty known-URL fallback still returns credentials

Listing-called alone would miss a path that enumerates Keystore then drops the results.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-27 10:58:22 +08:00

90 lines
3.2 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {CallsManager} from '@calls/calls_manager';
import DatabaseManager from '@database/manager';
import CallsNative from '@init/calls_native';
import {getAllServerCredentials} from '@init/credentials';
import ManagedApp from '@init/managed_app';
import PushNotifications from '@init/push_notifications';
import EphemeralModeManager from '@managers/ephemeral_mode_manager';
import GlobalEventHandler from '@managers/global_event_handler';
import NetworkManager from '@managers/network_manager';
import SecurityManager from '@managers/security_manager';
import SessionAttributesManager from '@managers/session_attributes_manager';
import SessionManager from '@managers/session_manager';
import WebsocketManager from '@managers/websocket_manager';
import {queryAllActiveServers} from '@queries/app/servers';
import EphemeralStore from '@store/ephemeral_store';
import {NavigationStore} from '@store/navigation_store';
// Controls whether the main initialization (database, etc...) is done, either on app launch
// or on the Share Extension, for example.
let baseAppInitialized = false;
let serverCredentials: ServerCredential[] = [];
// Fallback Polyfill for Promise.allSettle
Promise.allSettled = Promise.allSettled || (<T>(promises: Array<Promise<T>>) => Promise.all(
promises.map((p) => p.
then((value) => ({
status: 'fulfilled',
value,
})).
catch((reason) => ({
status: 'rejected',
reason,
})),
),
));
export async function initialize() {
if (!baseAppInitialized) {
baseAppInitialized = true;
try {
await DatabaseManager.initAppDatabase();
// Keystore entries with no matching active DB row are skipped (accepted vs listing every service).
const activeUrls = (await queryAllActiveServers()?.fetch() ?? []).map((s) => s.url);
serverCredentials = await getAllServerCredentials(activeUrls);
await DatabaseManager.initServerDatabases(serverCredentials.map((c) => c.serverUrl));
await NetworkManager.init(serverCredentials);
// EphemeralModeManager init runs before WS init so any pending wipes
// complete before WebSocket clients start populating server databases.
await EphemeralModeManager.init(serverCredentials);
await WebsocketManager.init(serverCredentials);
} catch (error) {
baseAppInitialized = false;
throw error;
}
}
NavigationStore.reset();
EphemeralStore.setCurrentThreadId('');
EphemeralStore.setProcessingNotification('');
await SecurityManager.init();
await SessionAttributesManager.syncStaticValues();
GlobalEventHandler.init();
ManagedApp.init();
SessionManager.init();
CallsManager.initialize();
CallsNative.init();
PushNotifications.init(serverCredentials.length > 0);
}
export function cleanup() {
ManagedApp.cleanup();
GlobalEventHandler.cleanup();
SecurityManager.cleanup();
SessionManager.cleanup();
CallsManager.cleanup();
CallsNative.cleanup();
PushNotifications.cleanup();
EphemeralModeManager.cleanup();
}