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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ Gotchas:
it is not namespaced for you. A collision does not throw: the second
registration is silently dropped with a console warning, so pick something
specific to your app (`myapp-image`, not `image`).
- Registering the same handler again, with the same `id` and `tagname`, is
quietly ignored. That is what happens when several copies of the package
on a page register the defaults, so there is nothing to guard against.
- Registering after the viewer has already read the handler list is not an
error either — the handler just never appears in the "Open with …" menu.
See [step 3](#3-load-your-registration-before-the-viewer) below for why
Expand Down
3 changes: 2 additions & 1 deletion __tests__/fileActions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ describe('registerHandler registry', () => {
const warn = vi.spyOn(logger, 'warn').mockImplementation(() => {})

registerHandler(makeHandler({ id: 'dup' }))
registerHandler(makeHandler({ id: 'dup' }))
// Another handler, not the same one registered twice
registerHandler(makeHandler({ id: 'dup', tagname: 'other-app-dup' }))

expect(warn).toHaveBeenCalledTimes(1)
expect(warn).toHaveBeenCalledWith(expect.stringContaining('dup'))
Expand Down
42 changes: 42 additions & 0 deletions __tests__/registerHandler.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*!
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { afterEach, describe, expect, it, vi } from 'vitest'
import { registerHandler } from '../lib/handlers.ts'
import { scope } from '../lib/scope.ts'
import { logger } from '../lib/services/logger.ts'
import { makeHandler } from './factories.ts'

describe('registering a handler whose id is taken', () => {
afterEach(() => {
vi.restoreAllMocks()
})

it('quietly keeps the first when it is the same handler again', () => {
// Several copies of the package on a page each register the
// defaults: that is one handler, not a collision
const warn = vi.spyOn(logger, 'warn')
const first = makeHandler({ id: 'repeat', tagname: 'oca-viewer-repeat' })
registerHandler(first)
registerHandler(makeHandler({ id: 'repeat', tagname: 'oca-viewer-repeat' }))

expect(scope.handlers!.get('repeat')).toBe(first)
expect(warn).not.toHaveBeenCalled()
})

it('lets a second copy of the package register the defaults without a word', async () => {
// The server registers them on every page, and an app bundling its
// own copy for older servers asks again
const { registerDefaultHandlers } = await import('../lib/defaults.ts')
registerDefaultHandlers()
vi.resetModules()
const second = await import('../lib/defaults.ts')
const { logger: secondLogger } = await import('../lib/services/logger.ts')
const warn = vi.spyOn(secondLogger, 'warn')

second.registerDefaultHandlers()

expect(warn).not.toHaveBeenCalled()
})
})
13 changes: 11 additions & 2 deletions lib/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,17 @@ export function registerHandler(handler: IHandler): void {
validateHandler(handler)

scope.handlers ??= new Map<string, IHandler>()
if (scope.handlers.has(handler.id)) {
logger.warn(`Handler with id ${handler.id} is already registered.`)
const registered = scope.handlers.get(handler.id)
if (registered !== undefined) {
// Every app bundles its own copy of the package, so the same handler
// can be registered more than once: the server's copy and an app's
// both register the defaults. The first one stays, as the custom
// element its tagname names is the first copy's too.
if (registered.tagname === handler.tagname) {
logger.debug(`Handler ${handler.id} is already registered, keeping the first registration`)
} else {
logger.warn(`Handler with id ${handler.id} is already registered for <${registered.tagname}>, ignoring the one for <${handler.tagname}>.`)
}
return
}

Expand Down
Loading