{"record":{"id":"dbfa65e8e4a6e8a5","repo":"RocketChat/Rocket.Chat","slug":"invalid-banner","errorCode":null,"errorMessage":"invalid banner","messagePattern":"invalid banner","errorType":"exception","errorClass":"Error","httpStatus":500,"severity":"error","filePath":"apps/meteor/server/modules/core-apps/banner.module.ts","lineNumber":20,"sourceCode":"import type { IUiKitCoreApp, UiKitCoreAppViewClosedPayload } from '@rocket.chat/core-services';\nimport type * as UiKit from '@rocket.chat/ui-kit';\n\nexport class BannerModule implements IUiKitCoreApp {\n\tappId = 'banner-core';\n\n\t// when banner view is closed we need to dismiss that banner for that user\n\tasync viewClosed(payload: UiKitCoreAppViewClosedPayload): Promise<UiKit.ServerInteraction> {\n\t\tconst {\n\t\t\tpayload: { view: { viewId: bannerId } = {} },\n\t\t\tuser: { _id: userId } = {},\n\t\t} = payload;\n\n\t\tif (!userId) {\n\t\t\tthrow new Error('invalid user');\n\t\t}\n\n\t\tif (!bannerId) {\n\t\t\tthrow new Error('invalid banner');\n\t\t}\n\n\t\tif (!payload.triggerId) {\n\t\t\tthrow new Error('invalid triggerId');\n\t\t}\n\n\t\tawait Banner.dismiss(userId, bannerId);\n\n\t\treturn {\n\t\t\ttype: 'banner.close',\n\t\t\ttriggerId: payload.triggerId,\n\t\t\tappId: payload.appId,\n\t\t\tviewId: bannerId,\n\t\t};\n\t}\n}\n","sourceCodeStart":2,"sourceCodeEnd":37,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/modules/core-apps/banner.module.ts#L2-L37","documentation":"`BannerModule.viewClosed` (`banner-core` core app) throws `Error('invalid banner')` when the nested `payload.payload.view.viewId` is missing. The viewId identifies which banner record to dismiss via `Banner.dismiss(userId, bannerId)`, so a close event without it cannot be routed to a banner.","triggerScenarios":"A `viewClosed` interaction for `banner-core` where `view` or `view.viewId` is undefined — e.g. a modal-close payload shape (which carries `view.id`) reused for a banner, or a manually constructed payload that omits the view.","commonSituations":"Frontend code or apps sending `view: { id }` instead of `view: { viewId }`; payloads stripped by a JSON round-trip or logger; test fixtures that only set triggerId.","solutions":["Send the banner's identifier as `payload: { view: { viewId } }` in the viewClosed payload.","Fetch the active banner (e.g. via the banners API) and use its `viewId` when building the close interaction.","For server-side dismissal without a UI surface, call `Banner.dismiss(userId, bannerId)` directly."],"exampleFix":"// before\npayload: { view: { id: bannerId }, triggerId }\n\n// after\npayload: { view: { viewId: bannerId }, triggerId }","handlingStrategy":"validation","validationCode":"if (!payload.payload?.view?.viewId) {\n  throw new Error('viewId is required to close a banner-core banner');\n}\nawait bannerModule.viewClosed(payload);","typeGuard":"const hasBannerViewId = (\n  payload: UiKitCoreAppViewClosedPayload,\n): payload is UiKitCoreAppViewClosedPayload & { payload: { view: { viewId: string } } } =>\n  Boolean(payload.payload?.view?.viewId);","tryCatchPattern":"try {\n  await bannerModule.viewClosed(payload);\n} catch (error) {\n  if (error instanceof Error && error.message === 'invalid banner') {\n    // rebuild the payload with view.viewId and retry\n  }\n}","preventionTips":["Use view.viewId (not view.id) for banner-core close payloads","Derive the viewId from the banner that was actually rendered","Add payload schema checks in tests that exercise banner dismissal"],"tags":["uikit","banner","payload-validation","core-apps"],"backgroundTag":"uikit-interaction-payload-invalid","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}