Line data Source code
1 : import * as React from 'react';
2 :
3 : import { useConnectionAllocator, useConnectionRegistryStore } from '../connections/connection_registry.js';
4 : import { useConnectionSaver } from '../connections/connection_persistence.js';
5 : import { connectionLabel } from '../connections/connection_label.js';
6 : import { useDashQLCoreSetup } from '../runtime/core_provider.js';
7 : import { OPEN_LINK_SESSION, useRouteContext, useRouterNavigate } from './router/router.js';
8 : import { sessionConnectionIds, type SessionScripts } from '../session/scripts/session_scripts.js';
9 : import { useSessionScriptsRegistryReader } from '../session/scripts/session_scripts_registry.js';
10 : import { useSessionCreation } from '../session/session_creation.js';
11 : import type { ConnectionState } from '../connections/connection_state.js';
12 : import { usePlatformEventListener } from '../platform/events/event_listener_provider.js';
13 : import { useLogger } from '../platform/logger/logger_provider.js';
14 : import { stringifyError } from '../platform/logger/logger.js';
15 : import { bindHyperLink, confirmHyperLink, getHyperLinkTabState, isHyperLinkParams, setHyperLinkToken, type HyperLinkTarget } from '../connections/hyper/hyper_link.js';
16 :
17 1 : const LOG_CTX = 'hyper_link';
18 :
19 : /// The session that a link opens: the open session if it runs on the connection of the link, otherwise the first
20 : /// session on it, null if a new one is needed
21 1 : export function sessionOnConnection(sessions: Map<string, SessionScripts>, currentSessionId: string | null, connectionId: string): string | null {
22 1 : const current = currentSessionId == null ? undefined : sessions.get(currentSessionId);
23 1 : if (current != null && sessionConnectionIds(current).includes(connectionId)) return currentSessionId;
24 1 : for (const [sessionId, scripts] of sessions) {
25 1 : if (sessionConnectionIds(scripts).includes(connectionId)) return sessionId;
26 : }
27 1 : return null;
28 1 : }
29 :
30 : /// Ask whether a link reuses the connection of an earlier link
31 1 : function confirmReuse(connection: ConnectionState, target: HyperLinkTarget): boolean {
32 1 : const endpoint = 'hyper' in connection.params ? connection.params.hyper.endpoint : '';
33 1 : return globalThis.confirm(`Use the connection "${connectionLabel(connection)}" to ${endpoint} for the Hyper at ${target.endpoint}? Cancel creates a new connection.`);
34 1 : }
35 :
36 : /// Follow the Hyper links, of the page and of the native app.
37 : ///
38 : /// A link is handled once the app opened its first session: a link to another machine is confirmed, the link is bound
39 : /// to a connection (see `bindHyperLink`), the connection takes the token of the link and new sessions start on it.
40 : /// The app then opens a session on the connection, or creates one.
41 1 : export function useHyperLinks(opened: Promise<void>): void {
42 1 : const appEvents = usePlatformEventListener();
43 1 : const logger = useLogger();
44 1 : const setupCore = useDashQLCoreSetup();
45 1 : const registryStore = useConnectionRegistryStore();
46 1 : const allocate = useConnectionAllocator();
47 1 : const saveConnection = useConnectionSaver();
48 1 : const readSessions = useSessionScriptsRegistryReader();
49 1 : const createSession = useSessionCreation();
50 1 : const navigate = useRouterNavigate();
51 1 : const route = useRouteContext();
52 :
53 1 : const follow = React.useEffectEvent(async (target: HyperLinkTarget) => {
54 1 : const dql = await setupCore(LOG_CTX);
55 1 : const connections = [...registryStore.current.connections.values()];
56 1 : const known = connections.some(connection => isHyperLinkParams(connection.params)
57 1 : && 'hyper' in connection.params && connection.params.hyper.endpoint === target.endpoint);
58 1 : if (!known && !confirmHyperLink(target)) return;
59 1 : const binding = bindHyperLink(target, connections, allocate, dql, getHyperLinkTabState().current,
60 1 : connection => confirmReuse(connection, target));
61 1 : if (binding.params != null) await saveConnection(binding.connection.connectionId, binding.params);
62 1 : setHyperLinkToken(binding.connection.connectionId, target.token);
63 1 : const sessions = readSessions().sessionScriptsMap;
64 1 : const sessionId = sessionOnConnection(sessions, route.sessionId ?? null, binding.connection.connectionId)
65 1 : ?? createSession(binding.connection).sessionId;
66 1 : if (sessionId !== route.sessionId) navigate({ type: OPEN_LINK_SESSION, value: sessionId });
67 1 : });
68 :
69 1 : React.useEffect(() => appEvents.subscribeHyperLinks(target => {
70 1 : logger.info('Received a Hyper link', { endpoint: target.endpoint }, LOG_CTX);
71 1 : void opened
72 1 : .then(() => follow(target))
73 1 : .catch(error => logger.error('Failed to follow a Hyper link', { error: stringifyError(error) }, LOG_CTX));
74 1 : }), [appEvents, logger, opened]);
75 1 : }
|