diff --git a/app/ide-desktop/lib/dashboard/src/App.tsx b/app/ide-desktop/lib/dashboard/src/App.tsx
index a2a212460a6..955deadf72e 100644
--- a/app/ide-desktop/lib/dashboard/src/App.tsx
+++ b/app/ide-desktop/lib/dashboard/src/App.tsx
@@ -84,6 +84,7 @@ import * as termsOfServiceModal from '#/modals/TermsOfServiceModal'
import LocalBackend from '#/services/LocalBackend'
import * as projectManager from '#/services/ProjectManager'
+import ProjectManager from '#/services/ProjectManager'
import RemoteBackend from '#/services/RemoteBackend'
import * as appBaseUrl from '#/utilities/appBaseUrl'
@@ -165,19 +166,51 @@ export interface AppProps {
* This component handles all the initialization and rendering of the app, and manages the app's
* routes. It also initializes an `AuthProvider` that will be used by the rest of the app. */
export default function App(props: AppProps) {
- const { supportsLocalBackend } = props
-
- const { data: rootDirectoryPath } = reactQuery.useSuspenseQuery({
- queryKey: ['root-directory', supportsLocalBackend],
+ const {
+ data: { projectManagerRootDirectory, projectManagerInstance },
+ } = reactQuery.useSuspenseQuery<{
+ projectManagerInstance: ProjectManager | null
+ projectManagerRootDirectory: projectManager.Path | null
+ }>({
+ queryKey: [
+ 'root-directory',
+ {
+ projectManagerUrl: props.projectManagerUrl,
+ supportsLocalBackend: props.supportsLocalBackend,
+ },
+ ] as const,
meta: { persist: false },
networkMode: 'always',
+ staleTime: Infinity,
+ gcTime: Infinity,
+ refetchOnMount: false,
+ refetchInterval: false,
+ refetchOnReconnect: false,
+ refetchIntervalInBackground: false,
+ behavior: {
+ onFetch: ({ state }) => {
+ const instance = state.data?.projectManagerInstance ?? null
+
+ if (instance != null) {
+ void instance.dispose()
+ }
+ },
+ },
queryFn: async () => {
- if (supportsLocalBackend) {
+ if (props.supportsLocalBackend && props.projectManagerUrl != null) {
const response = await fetch(`${appBaseUrl.APP_BASE_URL}/api/root-directory`)
const text = await response.text()
- return projectManager.Path(text)
+ const rootDirectory = projectManager.Path(text)
+
+ return {
+ projectManagerInstance: new ProjectManager(props.projectManagerUrl, rootDirectory),
+ projectManagerRootDirectory: rootDirectory,
+ }
} else {
- return null
+ return {
+ projectManagerInstance: null,
+ projectManagerRootDirectory: null,
+ }
}
},
})
@@ -199,7 +232,11 @@ export default function App(props: AppProps) {
-
+
@@ -214,6 +251,7 @@ export default function App(props: AppProps) {
/** Props for an {@link AppRouter}. */
export interface AppRouterProps extends AppProps {
readonly projectManagerRootDirectory: projectManager.Path | null
+ readonly projectManagerInstance: ProjectManager | null
}
/** Router definition for the app.
@@ -223,7 +261,7 @@ export interface AppRouterProps extends AppProps {
* component as the component that defines the provider. */
function AppRouter(props: AppRouterProps) {
const { logger, isAuthenticationDisabled, shouldShowDashboard, httpClient } = props
- const { onAuthenticated, projectManagerUrl, projectManagerRootDirectory } = props
+ const { onAuthenticated, projectManagerInstance } = props
const { portalRoot } = props
// `navigateHooks.useNavigate` cannot be used here as it relies on `AuthProvider`, which has not
// yet been initialized at this point.
@@ -235,11 +273,8 @@ function AppRouter(props: AppRouterProps) {
const navigator2D = navigator2DProvider.useNavigator2D()
const localBackend = React.useMemo(
- () =>
- projectManagerUrl != null && projectManagerRootDirectory != null
- ? new LocalBackend(projectManagerUrl, projectManagerRootDirectory)
- : null,
- [projectManagerUrl, projectManagerRootDirectory]
+ () => (projectManagerInstance != null ? new LocalBackend(projectManagerInstance) : null),
+ [projectManagerInstance]
)
const remoteBackend = React.useMemo(
diff --git a/app/ide-desktop/lib/dashboard/src/services/LocalBackend.ts b/app/ide-desktop/lib/dashboard/src/services/LocalBackend.ts
index 0d3dbb6010d..33608b58d99 100644
--- a/app/ide-desktop/lib/dashboard/src/services/LocalBackend.ts
+++ b/app/ide-desktop/lib/dashboard/src/services/LocalBackend.ts
@@ -5,7 +5,7 @@
* the API. */
import Backend, * as backend from '#/services/Backend'
import * as projectManager from '#/services/ProjectManager'
-import ProjectManager from '#/services/ProjectManager'
+import type ProjectManager from '#/services/ProjectManager'
import * as appBaseUrl from '#/utilities/appBaseUrl'
import * as dateTime from '#/utilities/dateTime'
@@ -96,9 +96,10 @@ export default class LocalBackend extends Backend {
private readonly projectManager: ProjectManager
/** Create a {@link LocalBackend}. */
- constructor(projectManagerUrl: string, rootDirectory: projectManager.Path) {
+ constructor(projectManagerInstance: ProjectManager) {
super()
- this.projectManager = new ProjectManager(projectManagerUrl, rootDirectory)
+
+ this.projectManager = projectManagerInstance
}
/** Get the root directory of this Backend as a path. */
diff --git a/app/ide-desktop/lib/dashboard/src/services/ProjectManager.ts b/app/ide-desktop/lib/dashboard/src/services/ProjectManager.ts
index b2876a918dd..e75190df3d9 100644
--- a/app/ide-desktop/lib/dashboard/src/services/ProjectManager.ts
+++ b/app/ide-desktop/lib/dashboard/src/services/ProjectManager.ts
@@ -347,6 +347,14 @@ export default class ProjectManager {
this.socketPromise = createSocket()
}
+ /**
+ * Dispose of the {@link ProjectManager}.
+ */
+ async dispose() {
+ const socket = await this.socketPromise
+ socket.close()
+ }
+
/** Open an existing project. */
async openProject(params: OpenProjectParams): Promise {
const cached = this.internalProjects.get(params.projectId)