From 8718bc3a3b41c60187969f05a60c91975244eb4f Mon Sep 17 00:00:00 2001 From: Jannik Stehle Date: Wed, 5 Nov 2025 09:35:22 +0100 Subject: [PATCH] fix: endless propfind requests when opening public links authenticated This issue was introduced by the authenticated config loading because it delayed the space loading. This change makes sure both things happen in parallel. Additionally, changes the `getResourceContext` method to a task so it gets cancelled on unmount, reducing the likelihood of such an endless loop even more. (cherry picked from commit 62ae8612fe7d57eb1d113669b422365486340ad3) --- .../src/composables/piniaStores/spaces.ts | 1 - .../resources/useGetResourceContext.ts | 22 +++++++++------ .../composables/piniaStores/spaces.spec.ts | 1 - packages/web-runtime/src/index.ts | 28 +++++++++---------- 4 files changed, 28 insertions(+), 24 deletions(-) diff --git a/packages/web-pkg/src/composables/piniaStores/spaces.ts b/packages/web-pkg/src/composables/piniaStores/spaces.ts index 063af03810..e3c7c0c1c6 100644 --- a/packages/web-pkg/src/composables/piniaStores/spaces.ts +++ b/packages/web-pkg/src/composables/piniaStores/spaces.ts @@ -215,7 +215,6 @@ export const useSpacesStore = defineStore('spaces', () => { ]) addSpaces([...personalSpaces, ...projectSpaces]) - spacesInitialized.value = true } finally { spacesLoading.value = false } diff --git a/packages/web-pkg/src/composables/resources/useGetResourceContext.ts b/packages/web-pkg/src/composables/resources/useGetResourceContext.ts index eeac8f29e1..f57eeba941 100644 --- a/packages/web-pkg/src/composables/resources/useGetResourceContext.ts +++ b/packages/web-pkg/src/composables/resources/useGetResourceContext.ts @@ -9,6 +9,7 @@ import { useClientService } from '../clientService' import { urlJoin } from '@opencloud-eu/web-client' import { useSpacesStore } from '../piniaStores' import { DavProperty } from '@opencloud-eu/web-client/webdav' +import { useTask } from 'vue-concurrency' export const useGetResourceContext = () => { const clientService = useClientService() @@ -25,7 +26,7 @@ export const useGetResourceContext = () => { ) } - const loadFileInfoById = (fileId: string) => { + const loadFileInfoById = (fileId: string, signal?: AbortSignal) => { const davProperties = [ DavProperty.FileId, DavProperty.FileParent, @@ -34,32 +35,32 @@ export const useGetResourceContext = () => { ] const tmpSpace = buildSpace({ id: fileId, name: '' }) - return clientService.webdav.getFileInfo(tmpSpace, { fileId }, { davProperties }) + return clientService.webdav.getFileInfo(tmpSpace, { fileId }, { davProperties, signal }) } // get context for a resource when only having its id. be careful, this might be very expensive! - const getResourceContext = async (id: string) => { + const resourceContextTask = useTask(function* (signal, id) { let path: string let resource: Resource let space = getMatchingSpaceByFileId(id) if (space) { - path = await clientService.webdav.getPathForFileId(id) - resource = await clientService.webdav.getFileInfo(space, { path }) + path = yield clientService.webdav.getPathForFileId(id, { signal }) + resource = yield clientService.webdav.getFileInfo(space, { path }, { signal }) return { space, resource, path } } // no matching space found => the file doesn't lie in own spaces => it's a share. // do PROPFINDs on parents until root of accepted share is found in `mountpoint` spaces - await spacesStore.loadMountPoints({ graphClient: clientService.graphAuthenticated }) + yield spacesStore.loadMountPoints({ graphClient: clientService.graphAuthenticated, signal }) let mountPoint = getMatchingMountPoint(id) - resource = await loadFileInfoById(id) + resource = yield loadFileInfoById(id, signal) const sharePathSegments = mountPoint ? [] : [unref(resource).name] let tmpResource = unref(resource) while (!mountPoint) { - tmpResource = await loadFileInfoById(tmpResource.parentFolderId) + tmpResource = yield loadFileInfoById(tmpResource.parentFolderId, signal) mountPoint = getMatchingMountPoint(tmpResource.id) if (!mountPoint) { sharePathSegments.unshift(tmpResource.name) @@ -76,6 +77,11 @@ export const useGetResourceContext = () => { path = urlJoin(...sharePathSegments) return { space, resource, path } + }).restartable() + + // get context for a resource when only having its id. be careful, this might be very expensive! + const getResourceContext = (id: string) => { + return resourceContextTask.perform(id) } return { diff --git a/packages/web-pkg/tests/unit/composables/piniaStores/spaces.spec.ts b/packages/web-pkg/tests/unit/composables/piniaStores/spaces.spec.ts index 7eda067fc7..3d65142838 100644 --- a/packages/web-pkg/tests/unit/composables/piniaStores/spaces.spec.ts +++ b/packages/web-pkg/tests/unit/composables/piniaStores/spaces.spec.ts @@ -200,7 +200,6 @@ describe('spaces', () => { ) expect(instance.spaces.length).toBe(2) expect(instance.spacesLoading).toBeFalsy() - expect(instance.spacesInitialized).toBeTruthy() } }) }) diff --git a/packages/web-runtime/src/index.ts b/packages/web-runtime/src/index.ts index 40cebdb53f..283761e835 100644 --- a/packages/web-runtime/src/index.ts +++ b/packages/web-runtime/src/index.ts @@ -46,6 +46,7 @@ import Avatar from './components/Avatar.vue' import { extensionPoints } from './extensionPoints' import { isSilentRedirectRoute } from './helpers/silentRedirect' import { extensions } from './extensions' +import { UnifiedRoleDefinition } from '@opencloud-eu/web-client/graph/generated' export const bootstrapApp = async (configurationPath: string, appsReadyCallback: () => void) => { const isSilentRedirect = isSilentRedirectRoute() @@ -202,13 +203,18 @@ export const bootstrapApp = async (configurationPath: string, appsReadyCallback: return } - await announceConfiguration({ - path: configurationPath, - configStore, - token: authStore.accessToken - }) - const clientService = app.config.globalProperties.$clientService + + const [graphRoleDefinitions] = await Promise.all([ + clientService.graphAuthenticated.permissions.listRoleDefinitions(), + spacesStore.loadSpaces({ graphClient: clientService.graphAuthenticated }), + announceConfiguration({ + path: configurationPath, + configStore, + token: authStore.accessToken + }) + ]) + const previewService = app.config.globalProperties.$previewService const passwordPolicyService = app.config.globalProperties.passwordPolicyService passwordPolicyService.initialize(capabilityStore) @@ -229,15 +235,8 @@ export const bootstrapApp = async (configurationPath: string, appsReadyCallback: }) } - // load sharing roles from graph API - const graphRoleDefinitions = - await clientService.graphAuthenticated.permissions.listRoleDefinitions() - sharesStore.setGraphRoles(graphRoleDefinitions) - - // Load spaces to make them available across the application - await spacesStore.loadSpaces({ graphClient: clientService.graphAuthenticated }) + sharesStore.setGraphRoles(graphRoleDefinitions as UnifiedRoleDefinition[]) const personalSpace = spacesStore.spaces.find(isPersonalSpaceResource) - if (personalSpace) { spacesStore.updateSpaceField({ id: personalSpace.id, @@ -245,6 +244,7 @@ export const bootstrapApp = async (configurationPath: string, appsReadyCallback: value: app.config.globalProperties.$gettext('Personal') }) } + spacesStore.setSpacesInitialized(true) }, { immediate: true