diff --git a/authentik/core/templates/base/header_js.html b/authentik/core/templates/base/header_js.html index 39dc374cf0..13d4eaafb2 100644 --- a/authentik/core/templates/base/header_js.html +++ b/authentik/core/templates/base/header_js.html @@ -16,18 +16,14 @@ relBase: "{{ base_url_rel }}", }, }; - window.addEventListener("DOMContentLoaded", function () { - {% for message in messages %} - window.dispatchEvent( - new CustomEvent("ak-message", { - bubbles: true, - composed: true, - detail: { - level: "{{ message.tags|escapejs }}", - message: "{{ message.message|escapejs }}", - }, - }), - ); - {% endfor %} - }); + + + diff --git a/web/src/common/objects.ts b/web/src/common/objects.ts index 8d826011c5..279e7b9ae0 100644 --- a/web/src/common/objects.ts +++ b/web/src/common/objects.ts @@ -57,3 +57,23 @@ export function trimMany(target: T, ...keys return output as Pick; } + +/** + * Try to parse a JSON string, returning a fallback value if parsing fails or if the input is not a string. + * + * @param input The input to parse. + * @param fallback The fallback value to return if parsing fails or if the input is not a string. Defaults to `null`. + * + * @returns The parsed value, or the fallback value if parsing fails or if the input is not a string. + */ +export function tryParsingJSON(input: unknown, fallback?: undefined): T | undefined; +export function tryParsingJSON(input: unknown, fallback: null): T | null; +export function tryParsingJSON(input: unknown, fallback?: F): T | F { + if (typeof input !== "string") return (fallback ?? null) as F; + + try { + return JSON.parse(input); + } catch { + return (fallback ?? null) as F; + } +} diff --git a/web/src/elements/messages/MessageContainer.ts b/web/src/elements/messages/MessageContainer.ts index 06ef03d7b1..48e174cebd 100644 --- a/web/src/elements/messages/MessageContainer.ts +++ b/web/src/elements/messages/MessageContainer.ts @@ -2,6 +2,7 @@ import "#elements/messages/Message"; import { parseAPIResponseError, pluckErrorDetail } from "#common/errors/network"; import { APIMessage, MessageLevel } from "#common/messages"; +import { tryParsingJSON } from "#common/objects"; import { AKElement } from "#elements/Base"; import Styles from "#elements/messages/styles.css"; @@ -106,6 +107,8 @@ export type MessageContainerAlignment = "top-left" | "top-right" | "bottom-left" @customElement("ak-message-container") export class MessageContainer extends AKElement { + public static readonly serializedSelector = "script[data-id=authentik-messages]"; + @property({ attribute: false }) public messages: APIMessage[] = []; @@ -129,8 +132,30 @@ export class MessageContainer extends AKElement { super.connectedCallback(); this.popover = "manual"; + + requestAnimationFrame(this.drainMessages); } + protected drainMessages = (): void => { + const selector = (this.constructor as typeof MessageContainer).serializedSelector; + const container = this.ownerDocument.querySelector(selector); + + if (!container) { + logger.warn(`Expected to find a script tag with ${selector}, but none was found.`); + return; + } + + const messages = tryParsingJSON(container.textContent); + + if (!messages?.length) { + return; + } + + for (const message of messages) { + this.addMessage(message); + } + }; + public updated(changedProperties: PropertyValues) { super.updated(changedProperties); diff --git a/web/src/styles/authentik/base/globals.css b/web/src/styles/authentik/base/globals.css index 204766507f..b6afc6f886 100644 --- a/web/src/styles/authentik/base/globals.css +++ b/web/src/styles/authentik/base/globals.css @@ -36,6 +36,24 @@ body { overflow: hidden; } +.ak-c-safe-mode { + position: fixed; + z-index: 9999999; + inset-block-end: 0; + inset-inline-end: 0; + pointer-events: none; + display: flex; + align-items: center; + justify-content: center; + font-size: var(--pf-global--FontSize--sm, 0.75rem); + font-weight: bold; + color: var(--pf-global--Color--100, CanvasText); + background-color: var(--pf-global--BackgroundColor--200, Canvas); + opacity: 0.75; + padding: var(--pf-global--spacer--sm, 0.5rem); + border-start-start-radius: 8px; +} + /* #endregion */ html > form > input {