Line data Source code
1 : import * as React from 'react';
2 :
3 : import type { Logger } from '../../platform/logger/logger.js';
4 : import { stringifyError } from '../../platform/logger/logger.js';
5 : import type { DynamicConnectionDispatch } from '../../connections/connection_registry.js';
6 : import { ConnectionHealth, type ConnectionState } from '../../connections/connection_state.js';
7 : import type { HyperSetupApi } from '../../connections/hyper/hyper_connection_setup.js';
8 : import { cancelConnectionSetup, registerConnectionSetup } from '../../connections/connection_setup_cancellation.js';
9 : import { isHyperLinkParams, useHyperLinkTabState } from '../../connections/hyper/hyper_link.js';
10 :
11 1 : const startupAttempts = new Map<string, AbortController>();
12 :
13 : // Settings can cancel the loader-owned attempt without creating a second setup controller.
14 1 : export function cancelInitialMemorySetup(connectionId: string): boolean {
15 1 : const abort = startupAttempts.get(connectionId);
16 1 : if (abort == null) return false;
17 1 : cancelConnectionSetup(connectionId);
18 1 : return true;
19 1 : }
20 :
21 : /// Set up the embedded database and the Hyper of a link once for the session the app opens with.
22 : /// Every connection of the session is considered, other connections are connected by the user.
23 1 : export function useInitialMemorySetup(
24 1 : sessionId: string | null,
25 1 : connections: readonly ConnectionState[] | null,
26 1 : setup: HyperSetupApi | null,
27 1 : dispatch: DynamicConnectionDispatch,
28 1 : logger: Logger,
29 1 : ): void {
30 1 : const hyperLinks = useHyperLinkTabState();
31 1 : const considered = React.useRef(new Set<string>());
32 1 : const attempts = React.useRef(new Map<string, AbortController>());
33 1 : const considerConnection = (database: ConnectionState, setup: HyperSetupApi) => {
34 1 : const params = database.params;
35 : // The connection to the Hyper of a link connects once per link, without the token of a link it waits for one
36 1 : const linked = isHyperLinkParams(params);
37 1 : const token = hyperLinks.tokens[database.connectionId];
38 1 : if (linked && token == null) return;
39 1 : const key = linked && 'hyper' in params ? `${database.connectionId} ${params.hyper.endpoint} ${token}` : database.connectionId;
40 1 : if (considered.current.has(key)) return;
41 : // Consume the initial selection even when online, cancelled, or carrying a draft.
42 : // Later resets, draft discards, and session switches must not opt back into setup.
43 1 : considered.current.add(key);
44 1 : if (database.paramsDraft != null
45 1 : || !('hyper' in params)
46 1 : || (params.hyper.protocol !== 'WASM' && !linked)) return;
47 1 : if (database.connectionHealth !== ConnectionHealth.NOT_STARTED) {
48 1 : if (!linked) return;
49 : // The connection speaks to the Hyper of an earlier link
50 1 : cancelConnectionSetup(database.connectionId);
51 1 : void setup.reset(action => dispatch(database.connectionId, action));
52 1 : }
53 1 : const abort = new AbortController();
54 1 : attempts.current.set(database.connectionId, abort);
55 1 : startupAttempts.set(database.connectionId, abort);
56 1 : const unregister = registerConnectionSetup(database.connectionId, abort);
57 1 : void setup.setup(
58 1 : action => dispatch(database.connectionId, action),
59 1 : structuredClone(params.hyper) as Parameters<HyperSetupApi['setup']>[1],
60 1 : abort.signal,
61 1 : database.connectionId,
62 1 : ).catch(error => {
63 1 : if (!abort.signal.aborted) logger.warn('Memory database setup failed', {
64 0 : sessionId, error: stringifyError(error),
65 0 : }, 'app_loader');
66 1 : }).finally(() => {
67 1 : unregister();
68 1 : attempts.current.delete(database.connectionId);
69 1 : if (startupAttempts.get(database.connectionId) === abort) startupAttempts.delete(database.connectionId);
70 1 : });
71 1 : };
72 1 : React.useEffect(() => {
73 1 : if (sessionId == null || connections == null || setup == null) return;
74 1 : for (const database of connections) {
75 1 : considerConnection(database, setup);
76 : }
77 1 : }, [connections, dispatch, hyperLinks, logger, sessionId, setup]);
78 1 : React.useEffect(() => () => {
79 1 : for (const [connectionId, abort] of attempts.current) {
80 1 : abort.abort(new DOMException('App loader unmounted', 'AbortError'));
81 1 : if (startupAttempts.get(connectionId) === abort) startupAttempts.delete(connectionId);
82 1 : }
83 1 : }, []);
84 1 : }
|