web: Fix server-side message race condition, type mismatch.

This commit is contained in:
Teffen Ellis
2026-06-17 03:35:28 +02:00
parent ee40107cd9
commit 01d4bb8918
4 changed files with 73 additions and 14 deletions
+10 -14
View File
@@ -16,18 +16,14 @@
relBase: "{{ base_url_rel }}", 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 %}
});
</script> </script>
<script data-id="authentik-messages" type="application/json">
[{% for message in messages %}
{
"level": "{{ message.tags|escapejs }}",
"message": "{{ message.message|escapejs }}"
}{% if not forloop.last %},{% endif %}
{% endfor %}]
</script>
+20
View File
@@ -57,3 +57,23 @@ export function trimMany<T extends object, K extends keyof T>(target: T, ...keys
return output as Pick<T, K>; return output as Pick<T, K>;
} }
/**
* 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<T = unknown>(input: unknown, fallback?: undefined): T | undefined;
export function tryParsingJSON<T = unknown>(input: unknown, fallback: null): T | null;
export function tryParsingJSON<T = unknown, F = null>(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;
}
}
@@ -2,6 +2,7 @@ import "#elements/messages/Message";
import { parseAPIResponseError, pluckErrorDetail } from "#common/errors/network"; import { parseAPIResponseError, pluckErrorDetail } from "#common/errors/network";
import { APIMessage, MessageLevel } from "#common/messages"; import { APIMessage, MessageLevel } from "#common/messages";
import { tryParsingJSON } from "#common/objects";
import { AKElement } from "#elements/Base"; import { AKElement } from "#elements/Base";
import Styles from "#elements/messages/styles.css"; import Styles from "#elements/messages/styles.css";
@@ -106,6 +107,8 @@ export type MessageContainerAlignment = "top-left" | "top-right" | "bottom-left"
@customElement("ak-message-container") @customElement("ak-message-container")
export class MessageContainer extends AKElement { export class MessageContainer extends AKElement {
public static readonly serializedSelector = "script[data-id=authentik-messages]";
@property({ attribute: false }) @property({ attribute: false })
public messages: APIMessage[] = []; public messages: APIMessage[] = [];
@@ -129,8 +132,30 @@ export class MessageContainer extends AKElement {
super.connectedCallback(); super.connectedCallback();
this.popover = "manual"; this.popover = "manual";
requestAnimationFrame(this.drainMessages);
} }
protected drainMessages = (): void => {
const selector = (this.constructor as typeof MessageContainer).serializedSelector;
const container = this.ownerDocument.querySelector<HTMLScriptElement>(selector);
if (!container) {
logger.warn(`Expected to find a script tag with ${selector}, but none was found.`);
return;
}
const messages = tryParsingJSON<APIMessage[]>(container.textContent);
if (!messages?.length) {
return;
}
for (const message of messages) {
this.addMessage(message);
}
};
public updated(changedProperties: PropertyValues<this>) { public updated(changedProperties: PropertyValues<this>) {
super.updated(changedProperties); super.updated(changedProperties);
+18
View File
@@ -36,6 +36,24 @@ body {
overflow: hidden; 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 */ /* #endregion */
html > form > input { html > form > input {