Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dev/docker/opencloud/csp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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}/'
Expand Down
7 changes: 6 additions & 1 deletion packages/web-pkg/src/components/SideBar/SideBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,13 @@
>
<component
:is="p.component"
v-if="[activePanelName, oldPanelName].includes(p.name)"
v-if="
hasActiveRootPanel
? p.isRoot?.(panelContext)
: [activePanelName, oldPanelName].includes(p.name)
"
:class="{ 'multi-root-panel-separator oc-mt oc-pt-s': index > 0 }"
class="oc-rounded"
v-bind="p.componentAttrs?.(panelContext) || {}"
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
}
Expand Down
52 changes: 42 additions & 10 deletions packages/web-runtime/src/container/application/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,26 +62,26 @@ const loadScriptRequireJS = <T>(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)
}
Expand Down Expand Up @@ -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) {
Expand Down
84 changes: 58 additions & 26 deletions packages/web-runtime/src/container/bootstrap.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -190,8 +191,9 @@ export const announceAuthClient = async (configStore: ConfigStore): Promise<void

/**
* announce applications to the runtime, it takes care that all requirements are fulfilled and then:
* - bulk build all applications
* - bulk register all applications, no other application is guaranteed to be registered here, don't request one
* - bulk loads all application scripts
* - builds and registers applications one by one (for a stable order)
* - bulk initializes all applications
*/
export const initializeApplications = async ({
app,
Expand All @@ -210,18 +212,25 @@ export const initializeApplications = async ({
path?: string
config?: AppConfigObject
}
type ApplicationResponse = {
appName?: string
applicationKey: string
applicationPath: string
applicationConfig: AppConfigObject
applicationScript: ClassicApplicationScript
}

let applicationResults: PromiseSettledResult<NextApplication>[] = []
let applicationKeys: string[] = []
let applicationResponses: PromiseSettledResult<ApplicationResponse>[] = []
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
})
)
Expand All @@ -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<ApplicationResponse[]>(
(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<NextApplication[]>((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()))

Expand Down
30 changes: 21 additions & 9 deletions packages/web-runtime/tests/unit/container/bootstrap.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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()
Expand All @@ -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)
Expand Down