LCOV - code coverage report
Current view: top level - dashql-app/src/app/router - router.ts Coverage Total Hit
Test: DashQL 915828b Lines: 100.0 % 172 172
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import * as React from 'react';
       2              : import { useLocation, useNavigate } from "react-router-dom";
       3              : import { VariantKind } from "../../utils/variant.js";
       4              : import { AppLoadingStatus } from '../loading/app_loading_status.js';
       5              : import { SessionSetupStatus } from '../loading/session_setup_status.js';
       6              : import { LoggableException } from '../../platform/logger/logger.js';
       7              : 
       8              : export interface RouteContext {
       9              :     /// The app loading status
      10              :     appLoadingStatus: AppLoadingStatus;
      11              :     /// Confirmed the finished setup?
      12              :     confirmedFinishedSetup: boolean;
      13              :     /// The focused session id
      14              :     sessionId: string | null;
      15              :     /// The session setup status
      16              :     sessionSetupStatus: SessionSetupStatus;
      17              : }
      18              : 
      19            1 : export const SESSION_PATH = Symbol("NAVIGATE_SESSION");
      20            1 : export const FINISH_SETUP = Symbol("FINISH_SETUP");
      21            1 : export const CONFIRM_FINISHED_SETUP = Symbol("CONFIRM_FINISHED_SETUP");
      22            1 : export const SKIP_SETUP = Symbol("SKIP_SETUP");
      23            1 : export const SELECT_SESSION = Symbol("SELECT_SESSION");
      24            1 : export const CHANGE_SESSION = Symbol("CHANGE_SESSION");
      25            1 : export const BEGIN_SESSION_SETUP = Symbol("BEGIN_SESSION_SETUP");
      26            1 : export const CANCEL_SESSION_SETUP = Symbol("CANCEL_SESSION_SETUP");
      27            1 : export const SKIP_SESSION_SETUP = Symbol("SKIP_SESSION_SETUP");
      28            1 : export const OPEN_LINK_SESSION = Symbol("OPEN_LINK_SESSION");
      29            1 : export const OPEN_SESSION = Symbol("OPEN_SESSION");
      30            1 : export const OPEN_EPHEMERAL_SESSION = Symbol("OPEN_EPHEMERAL_SESSION");
      31            1 : export const PROMOTE_SESSION = Symbol("PROMOTE_SESSION");
      32              : 
      33            1 : export function sessionPath(sessionId: string): string {
      34            1 :     return `/sessions/${encodeURIComponent(sessionId)}`;
      35            1 : }
      36              : 
      37            1 : export function sessionIdFromPathname(pathname: string): string | null {
      38            1 :     const match = /^\/sessions\/([^/]+)\/?$/.exec(pathname);
      39            1 :     if (match == null) return null;
      40            1 :     try {
      41            1 :         return decodeURIComponent(match[1]);
      42            1 :     } catch {
      43            1 :         return null;
      44            1 :     }
      45            1 : }
      46              : 
      47              : export type RouteTarget =
      48              :     VariantKind<typeof SESSION_PATH, string | null>
      49              :     | VariantKind<typeof FINISH_SETUP, null>
      50              :     | VariantKind<typeof CONFIRM_FINISHED_SETUP, boolean>
      51              :     | VariantKind<typeof SKIP_SETUP, null>
      52              :     | VariantKind<typeof SELECT_SESSION, string>
      53              :     | VariantKind<typeof CHANGE_SESSION, null>
      54              :     | VariantKind<typeof BEGIN_SESSION_SETUP, string>
      55              :     | VariantKind<typeof CANCEL_SESSION_SETUP, null>
      56              :     | VariantKind<typeof SKIP_SESSION_SETUP, null>
      57              :     | VariantKind<typeof OPEN_LINK_SESSION, string>
      58              :     | VariantKind<typeof OPEN_SESSION, string>
      59              :     | VariantKind<typeof OPEN_EPHEMERAL_SESSION, string>
      60              :     | VariantKind<typeof PROMOTE_SESSION, string>
      61              :     ;
      62              : 
      63            1 : export function useRouteContext() {
      64            1 :     const location = useLocation();
      65            1 :     const route = location.state as RouteContext;
      66            1 :     const pathname = location.pathname;
      67              :     // Memoized on the location, callers depend on the context in callbacks and effects
      68            1 :     return React.useMemo(() => {
      69            1 :         if (!route) {
      70            1 :             return {
      71            1 :                 appLoadingStatus: AppLoadingStatus.NOT_STARTED,
      72            1 :                 confirmedFinishedSetup: false,
      73            1 :                 sessionId: sessionIdFromPathname(pathname),
      74            1 :                 sessionSetupStatus: SessionSetupStatus.NONE,
      75            1 :             };
      76              :         } else {
      77            1 :             return {
      78            1 :                 ...route,
      79            1 :                 sessionSetupStatus: route.sessionSetupStatus ?? SessionSetupStatus.NONE,
      80            1 :             };
      81              :         }
      82            1 :     }, [route, pathname]);
      83            1 : }
      84              : 
      85            1 : export function useRouterNavigate() {
      86            1 :     const navigate = useNavigate();
      87            1 :     const location = useLocation();
      88            1 :     const context = useRouteContext();
      89            1 :     return React.useCallback((route: RouteTarget) => {
      90            1 :         switch (route.type) {
      91            1 :             case SESSION_PATH:
      92            1 :                 navigate(route.value == null ? "/" : sessionPath(route.value), {
      93            1 :                     state: {
      94            1 :                         ...context,
      95            1 :                         sessionId: route.value ?? null,
      96            1 :                     }
      97            1 :                 });
      98            1 :                 break;
      99            1 :             case SKIP_SETUP:
     100            1 :                 navigate(location.pathname, {
     101            1 :                     state: {
     102            1 :                         ...context,
     103            1 :                         appLoadingStatus: AppLoadingStatus.SETUP_DONE,
     104            1 :                     }
     105            1 :                 });
     106            1 :                 break;
     107            1 :             case FINISH_SETUP:
     108            1 :                 navigate(location.pathname, {
     109            1 :                     state: {
     110            1 :                         appLoadingStatus: AppLoadingStatus.SETUP_DONE,
     111            1 :                         confirmedFinishedSetup: false,
     112            1 :                         sessionId: null,
     113            1 :                         sessionSetupStatus: SessionSetupStatus.NONE,
     114            1 :                     }
     115            1 :                 });
     116            1 :                 break;
     117            1 :             case SELECT_SESSION:
     118            1 :                 navigate(sessionPath(route.value), {
     119            1 :                     state: {
     120            1 :                         ...context,
     121            1 :                         sessionId: route.value,
     122            1 :                         sessionSetupStatus: SessionSetupStatus.NONE,
     123            1 :                     }
     124            1 :                 });
     125            1 :                 break;
     126            1 :             case OPEN_SESSION:
     127            1 :                 navigate(sessionPath(route.value), {
     128            1 :                     state: {
     129            1 :                         ...context,
     130            1 :                         sessionId: route.value,
     131            1 :                         sessionSetupStatus: SessionSetupStatus.OPENING,
     132            1 :                     }
     133            1 :                 });
     134            1 :                 break;
     135            1 :             case CHANGE_SESSION:
     136            1 :                 navigate("/", {
     137            1 :                     state: {
     138            1 :                         ...context,
     139            1 :                         sessionId: null,
     140            1 :                         sessionSetupStatus: SessionSetupStatus.NONE,
     141            1 :                     }
     142            1 :                 });
     143            1 :                 break;
     144            1 :             case BEGIN_SESSION_SETUP:
     145            1 :                 navigate(sessionPath(route.value), {
     146            1 :                     state: {
     147            1 :                         ...context,
     148            1 :                         sessionId: route.value,
     149            1 :                         sessionSetupStatus: SessionSetupStatus.CONFIGURING,
     150            1 :                     }
     151            1 :                 });
     152            1 :                 break;
     153            1 :             case CANCEL_SESSION_SETUP:
     154            1 :                 navigate("/", {
     155            1 :                     state: {
     156            1 :                         ...context,
     157            1 :                         sessionId: null,
     158            1 :                         sessionSetupStatus: SessionSetupStatus.NONE,
     159            1 :                     }
     160            1 :                 });
     161            1 :                 break;
     162            1 :             case SKIP_SESSION_SETUP:
     163            1 :                 navigate(location.pathname, {
     164            1 :                     state: {
     165            1 :                         ...context,
     166            1 :                         sessionSetupStatus: SessionSetupStatus.NONE,
     167            1 :                     }
     168            1 :                 });
     169            1 :                 break;
     170            1 :             case OPEN_LINK_SESSION:
     171              :                 // A session arrived via a shared link and has been restored into the registries.
     172              :                 // Start the common opening flow atomically so embedded connections can initialize
     173              :                 // without briefly showing the connection setup screen.
     174            1 :                 navigate(sessionPath(route.value), {
     175            1 :                     state: {
     176            1 :                         appLoadingStatus: AppLoadingStatus.SETUP_DONE,
     177            1 :                         confirmedFinishedSetup: false,
     178            1 :                         sessionId: route.value,
     179            1 :                         sessionSetupStatus: SessionSetupStatus.OPENING,
     180            1 :                     }
     181            1 :                 });
     182            1 :                 break;
     183            1 :             case OPEN_EPHEMERAL_SESSION:
     184              :                 // The greeter stays at the root until a script promotes the session. The startup replaces its entry:
     185              :                 // going back to it would find a root without a route context and show the loading page.
     186              :                 // The status is not OPENING, the loader would open the path of the session.
     187            1 :                 navigate("/", {
     188            1 :                     replace: location.pathname === "/",
     189            1 :                     state: {
     190            1 :                         appLoadingStatus: AppLoadingStatus.SETUP_DONE,
     191            1 :                         confirmedFinishedSetup: context.confirmedFinishedSetup,
     192            1 :                         sessionId: route.value,
     193            1 :                         sessionSetupStatus: SessionSetupStatus.NONE,
     194            1 :                     }
     195            1 :                 });
     196            1 :                 break;
     197            1 :             case PROMOTE_SESSION:
     198              :                 // Replaces the root, going back would promote the session again
     199            1 :                 navigate(sessionPath(route.value), {
     200            1 :                     replace: true,
     201            1 :                     state: {
     202            1 :                         ...context,
     203            1 :                         sessionId: route.value,
     204            1 :                         sessionSetupStatus: SessionSetupStatus.NONE,
     205            1 :                     }
     206            1 :                 });
     207            1 :                 break;
     208            1 :             case CONFIRM_FINISHED_SETUP:
     209            1 :                 if (context.appLoadingStatus != AppLoadingStatus.SETUP_DONE) {
     210            1 :                     throw new LoggableException("tried to confirm a non-finished setup");
     211              :                 }
     212            1 :                 navigate(location.pathname, {
     213            1 :                     state: {
     214            1 :                         ...context,
     215            1 :                         appLoadingStatus: AppLoadingStatus.SETUP_DONE,
     216            1 :                         confirmedFinishedSetup: route.value,
     217            1 :                     }
     218            1 :                 });
     219              :                 break;
     220            1 :         }
     221            1 :     }, [navigate, context, location]);
     222            1 : }
        

Generated by: LCOV version 2.0-1