diff --git a/dev/docker/opencloud/csp.yaml b/dev/docker/opencloud/csp.yaml
index 01028fdb63..a5ec3a5ed1 100644
--- a/dev/docker/opencloud/csp.yaml
+++ b/dev/docker/opencloud/csp.yaml
@@ -23,6 +23,7 @@ directives:
- 'data:'
- 'blob:'
- 'https://raw.githubusercontent.com/opencloud-eu/awesome-apps/'
+ - 'https://tile.openstreetmap.org/'
# In contrast to bash and docker the default is given after the | character
- 'https://${ONLYOFFICE_DOMAIN|host.docker.internal:9981}/'
- 'https://${COLLABORA_DOMAIN|host.docker.internal:9980}/'
diff --git a/packages/web-pkg/src/components/SideBar/SideBar.vue b/packages/web-pkg/src/components/SideBar/SideBar.vue
index a717c702ec..327bd58177 100644
--- a/packages/web-pkg/src/components/SideBar/SideBar.vue
+++ b/packages/web-pkg/src/components/SideBar/SideBar.vue
@@ -71,8 +71,13 @@
>
diff --git a/packages/web-pkg/src/composables/actions/files/useFileActionsToggleHideShare.ts b/packages/web-pkg/src/composables/actions/files/useFileActionsToggleHideShare.ts
index 57c18ad324..83fc7ff8eb 100644
--- a/packages/web-pkg/src/composables/actions/files/useFileActionsToggleHideShare.ts
+++ b/packages/web-pkg/src/composables/actions/files/useFileActionsToggleHideShare.ts
@@ -64,9 +64,7 @@ export const useFileActionsToggleHideShare = () => {
}
showErrorMessage({
- title: hidden
- ? $gettext('Failed to hide the share')
- : $gettext('Failed to unhide share share'),
+ title: hidden ? $gettext('Failed to hide the share') : $gettext('Failed to unhide the share'),
errors
})
}
diff --git a/packages/web-runtime/src/container/application/index.ts b/packages/web-runtime/src/container/application/index.ts
index c7d4bb1cbc..82abce245e 100644
--- a/packages/web-runtime/src/container/application/index.ts
+++ b/packages/web-runtime/src/container/application/index.ts
@@ -62,26 +62,26 @@ const loadScriptRequireJS = (moduleUri: string) => {
)
)
}
-/**
- * sniffs arguments and decides if given manifest is of next or current application style.
- */
-export const buildApplication = async ({
- app,
+
+export const loadApplication = async ({
appName,
applicationKey,
applicationPath,
applicationConfig,
- router,
configStore
}: {
- app: App
appName?: string
applicationKey: string
applicationPath: string
applicationConfig: AppConfigObject
- router: Router
configStore: ConfigStore
-}) => {
+}): Promise<{
+ appName?: string
+ applicationKey: string
+ applicationPath: string
+ applicationConfig: AppConfigObject
+ applicationScript: ClassicApplicationScript
+}> => {
if (applicationStore.has(applicationKey)) {
throw new RuntimeError('application already announced', applicationKey, applicationPath)
}
@@ -118,8 +118,40 @@ export const buildApplication = async ({
throw new RuntimeError('cannot load application', applicationPath, e)
}
- let application: NextApplication
+ return {
+ appName,
+ applicationKey,
+ applicationPath,
+ applicationConfig,
+ applicationScript
+ }
+}
+
+/**
+ * sniffs arguments and decides if given manifest is of next or current application style.
+ */
+export const buildApplication = ({
+ app,
+ appName,
+ applicationKey,
+ applicationPath,
+ applicationConfig,
+ applicationScript,
+ router
+}: {
+ app: App
+ appName?: string
+ applicationKey: string
+ applicationPath: string
+ applicationConfig: AppConfigObject
+ applicationScript: ClassicApplicationScript
+ router: Router
+}) => {
+ if (applicationStore.has(applicationKey)) {
+ throw new RuntimeError('application already announced', applicationKey, applicationPath)
+ }
+ let application: NextApplication
try {
/** add valuable sniffer to detect next applications, then implement next application factory */
if (!isObject(applicationScript.appInfo) && !applicationScript.setup) {
diff --git a/packages/web-runtime/src/container/bootstrap.ts b/packages/web-runtime/src/container/bootstrap.ts
index 5ad5609cc2..9bedb2d1b4 100644
--- a/packages/web-runtime/src/container/bootstrap.ts
+++ b/packages/web-runtime/src/container/bootstrap.ts
@@ -1,5 +1,5 @@
import { registerClient } from '../services/clientRegistration'
-import { buildApplication, NextApplication } from './application'
+import { buildApplication, loadApplication, NextApplication } from './application'
import { RouteLocationRaw, Router, RouteRecordNormalized } from 'vue-router'
import { App, computed, watch } from 'vue'
import { loadTheme } from '../helpers/theme'
@@ -42,7 +42,8 @@ import {
UppyService,
AppConfigObject,
resourceIconMappingInjectionKey,
- ResourceIconMapping
+ ResourceIconMapping,
+ ClassicApplicationScript
} from '@opencloud-eu/web-pkg'
import { authService } from '../services/auth'
import { init as sentryInit } from '@sentry/vue'
@@ -190,8 +191,9 @@ export const announceAuthClient = async (configStore: ConfigStore): Promise[] = []
+ let applicationKeys: string[] = []
+ let applicationResponses: PromiseSettledResult[] = []
if (appProviderApps) {
- applicationResults = await Promise.allSettled(
+ applicationKeys = appProviderService.appNames.map((appName) => `web-app-external-${appName}`)
+ applicationResponses = await Promise.allSettled(
appProviderService.appNames.map((appName) =>
- buildApplication({
- app,
+ loadApplication({
appName,
applicationKey: `web-app-external-${appName}`,
applicationPath: 'web-app-external',
applicationConfig: {},
- router,
configStore
})
)
@@ -233,30 +242,53 @@ export const initializeApplications = async ({
})),
...configStore.externalApps
]
- applicationResults = await Promise.allSettled(
- rawApplications.map((rawApplication) =>
- buildApplication({
- app,
- applicationKey: rawApplication.path,
- applicationPath: rawApplication.path,
- applicationConfig: rawApplication.config,
- router,
+ applicationKeys = rawApplications.map((rawApplication) => rawApplication.path)
+ applicationResponses = await Promise.allSettled(
+ rawApplications.map((rawApplications) =>
+ loadApplication({
+ applicationKey: rawApplications.path,
+ applicationPath: rawApplications.path,
+ applicationConfig: rawApplications.config || {},
configStore
})
)
)
}
+ const applicationScripts = applicationResponses.reduce(
+ (acc, applicationResponse) => {
+ // we don't want to fail hard with the full system when one specific application can't get loaded. only log the error.
+ if (applicationResponse.status !== 'fulfilled') {
+ console.error(applicationResponse.reason)
+ } else {
+ acc.push(applicationResponse.value)
+ }
- const applications = applicationResults.reduce((acc, applicationResult) => {
- // we don't want to fail hard with the full system when one specific application can't get loaded. only log the error.
- if (applicationResult.status !== 'fulfilled') {
- console.error(applicationResult.reason)
- } else {
- acc.push(applicationResult.value)
- }
+ return acc
+ },
+ []
+ )
- return acc
- }, [])
+ // create applications in a stable order, one by one, so that apps and extensions get registered in the same order every time.
+ const applications: NextApplication[] = []
+ for (const applicationKey of applicationKeys) {
+ const applicationResponse = applicationScripts.find(
+ (appResponse) => appResponse.applicationKey === applicationKey
+ )
+ if (!applicationResponse) {
+ continue
+ }
+ try {
+ applications.push(
+ buildApplication({
+ ...applicationResponse,
+ app,
+ router
+ })
+ )
+ } catch (error) {
+ console.error(error)
+ }
+ }
await Promise.all(applications.map((application) => application.initialize()))
diff --git a/packages/web-runtime/tests/unit/container/bootstrap.spec.ts b/packages/web-runtime/tests/unit/container/bootstrap.spec.ts
index 1bebeee66a..20bc9416c5 100644
--- a/packages/web-runtime/tests/unit/container/bootstrap.spec.ts
+++ b/packages/web-runtime/tests/unit/container/bootstrap.spec.ts
@@ -8,7 +8,7 @@ import {
announceCustomStyles,
announceConfiguration
} from '../../../src/container/bootstrap'
-import { buildApplication } from '../../../src/container/application'
+import { buildApplication, loadApplication } from '../../../src/container/application'
import { createTestingPinia } from '@opencloud-eu/web-test-helpers'
vi.mock('../../../src/container/application')
@@ -24,16 +24,27 @@ describe('initialize applications', () => {
const initialize = vi.fn()
const ready = vi.fn()
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined)
- const buildApplicationMock = vi
+
+ const loadApplicationMock = vi
.fn()
- .mockImplementation(({ applicationPath }: { applicationPath: string }) => {
- if (applicationPath.includes('Valid')) {
- return Promise.resolve({ initialize, ready })
+ .mockImplementation(
+ ({
+ applicationKey,
+ applicationPath
+ }: {
+ applicationKey: string
+ applicationPath: string
+ }) => {
+ if (applicationPath.includes('Valid')) {
+ return Promise.resolve({ applicationKey })
+ }
+
+ return Promise.reject(fishyError)
}
+ )
+ vi.mocked(loadApplication).mockImplementation(loadApplicationMock)
- return Promise.reject(fishyError)
- })
-
+ const buildApplicationMock = vi.fn().mockReturnValue({ initialize, ready })
vi.mocked(buildApplication).mockImplementation(buildApplicationMock)
const configStore = useConfigStore()
@@ -50,7 +61,8 @@ describe('initialize applications', () => {
appProviderService: undefined
})
- expect(buildApplicationMock).toHaveBeenCalledTimes(4)
+ expect(loadApplicationMock).toHaveBeenCalledTimes(4)
+ expect(buildApplicationMock).toHaveBeenCalledTimes(2)
expect(initialize).toHaveBeenCalledTimes(2)
expect(errorSpy).toHaveBeenCalledTimes(2)
expect(errorSpy.mock.calls[0][0]).toMatchObject(fishyError)