Line data Source code
1 : import * as dashql from '@ankoh/dashql-core';
2 :
3 : import { TracedLogger } from '../../platform/logger/logger.js';
4 : import type { AppStateRestorationProgress, RestoredAppState } from '../../session/persistence/app_state_loader.js';
5 : import { AppLoadingProgress, AppLoadingProgressConsumer } from './app_loading_progress.js';
6 : import { SetConnectionRegistryAction } from '../../connections/connection_registry.js';
7 : import type { ConnectionState } from '../../connections/connection_state.js';
8 : import { Dispatch } from '../../utils/variant.js';
9 : import { SetSessionScriptsRegistryAction } from '../../session/scripts/session_scripts_registry.js';
10 : import { ProgressCounter } from '../../utils/progress.js';
11 : import { InvalidSession } from '../../storage/session_validation.js';
12 : import { AppLoadingStatus } from './app_loading_status.js';
13 : import type { RouteContext } from '../router/router.js';
14 : import { isEphemeralSession, type SessionScripts } from '../../session/scripts/session_scripts.js';
15 :
16 1 : export function canShowSession(route: RouteContext, pauseAfterSetup: boolean): boolean {
17 1 : return route.appLoadingStatus === AppLoadingStatus.SETUP_DONE
18 1 : && route.sessionId != null
19 1 : && (!pauseAfterSetup || route.confirmedFinishedSetup);
20 1 : }
21 :
22 : export interface AppLoadingResult {
23 : /// Sessions whose metadata failed validation and were refused a load (keyed by bare UUID).
24 : invalidSessions: Map<string, InvalidSession>;
25 : /// Valid restored sessions in persisted manifest order.
26 : restoredSessions: ReadonlyMap<string, SessionScripts>;
27 : /// The restored connections
28 : restoredConnections: Map<string, ConnectionState>;
29 : }
30 :
31 : /// The session that the app opens with: the restored session of the path, otherwise the app opens the greeter
32 1 : export function selectStartupSession(
33 1 : requestedSessionId: string | null,
34 1 : restoredSessions: ReadonlyMap<string, SessionScripts>,
35 1 : ): string | null {
36 1 : const requested = requestedSessionId == null ? undefined : restoredSessions.get(requestedSessionId);
37 1 : return requested != null && !isEphemeralSession(requested) ? requested.sessionId : null;
38 1 : }
39 :
40 : /// Delete the ephemeral sessions beyond the first one, the app keeps a single one.
41 : /// A session that cannot be deleted is reported and the next launch deletes it again, it stays hidden until then.
42 1 : export async function deleteSurplusEphemeralSessions(
43 1 : sessions: Iterable<SessionScripts>,
44 1 : deleteSession: (sessionId: string) => Promise<void>,
45 1 : onFailure: (sessionId: string, error: unknown) => void,
46 1 : ): Promise<void> {
47 1 : for (const { sessionId } of [...sessions].filter(isEphemeralSession).slice(1)) {
48 1 : try {
49 1 : await deleteSession(sessionId);
50 1 : } catch (error) {
51 1 : onFailure(sessionId, error);
52 1 : }
53 : }
54 1 : }
55 :
56 1 : export async function shouldFinishInitialNavigation(initialInlineSetup: Promise<boolean> | null): Promise<boolean> {
57 1 : return initialInlineSetup == null || !await initialInlineSetup;
58 1 : }
59 :
60 : /// Restores the persisted app state
61 : export interface AppStateRestorer {
62 : /// Restore the connections and sessions
63 : restoreAppState(core: dashql.DashQL, progressConsumer: (progress: AppStateRestorationProgress) => void): Promise<RestoredAppState>;
64 : }
65 :
66 : /// Main logic to setup the application
67 1 : export async function loadApp(logger: TracedLogger, core: dashql.DashQL, storage: AppStateRestorer, resetConnections: Dispatch<SetConnectionRegistryAction>, resetSessionScripts: Dispatch<SetSessionScriptsRegistryAction>, consumer: AppLoadingProgressConsumer) {
68 1 : const traced = logger.childSpan();
69 1 : traced.info("Loading application", {}, "app_loading");
70 1 : const appLoadStartTime = performance.now();
71 :
72 1 : let progress: AppLoadingProgress = {
73 1 : restoreConnections: new ProgressCounter(),
74 1 : restoreCatalogs: new ProgressCounter(),
75 1 : restoreSessionScripts: new ProgressCounter(),
76 1 : };
77 1 : const partialProgressConsumer = (update: Partial<AppLoadingProgress>) => {
78 1 : progress = {
79 1 : ...progress,
80 1 : ...update
81 1 : };
82 1 : consumer(progress);
83 1 : };
84 :
85 1 : traced.info("Restoring application state", {}, "app_loading");
86 1 : const restoreStartTime = performance.now();
87 :
88 : /// First restore the previous app state
89 1 : const state = await storage.restoreAppState(core, partialProgressConsumer);
90 :
91 1 : const restoreDuration = performance.now() - restoreStartTime;
92 1 : traced.info("Restored application state", {
93 1 : connections: state.connectionStates.size.toString(),
94 1 : sessions: state.sessionScripts.size.toString(),
95 1 : durationMs: restoreDuration.toFixed(2)
96 1 : }, "app_loading");
97 :
98 : // Reset the connection registry
99 1 : traced.info("Updating connection registry", {
100 1 : connectionCount: state.connectionStates.size.toString()
101 1 : }, "app_loading");
102 1 : resetConnections({
103 1 : connections: state.connectionStates,
104 1 : connectionsByType: state.connectionStatesByType,
105 1 : });
106 :
107 : // Reset the session scripts registry
108 1 : traced.info("Updating session scripts registry", {
109 1 : sessionCount: state.sessionScripts.size.toString()
110 1 : }, "app_loading");
111 1 : resetSessionScripts({
112 1 : sessionScriptsMap: state.sessionScripts,
113 1 : sessionScriptsByConnection: state.sessionScriptsByConnection,
114 1 : invalidSessions: state.invalidSessions,
115 1 : });
116 :
117 1 : const totalAppLoadDuration = performance.now() - appLoadStartTime;
118 1 : traced.info("Application loading complete", {
119 1 : totalDurationMs: totalAppLoadDuration.toFixed(2)
120 1 : }, "app_loading");
121 :
122 1 : return {
123 1 : invalidSessions: state.invalidSessions,
124 1 : restoredSessions: state.sessionScripts,
125 1 : restoredConnections: state.connectionStates,
126 1 : };
127 1 : }
|