diff --git a/web/src/common/ws.ts b/web/src/common/ws.ts new file mode 100644 index 000000000..d9dc5004e --- /dev/null +++ b/web/src/common/ws.ts @@ -0,0 +1,61 @@ +import { t } from "@lingui/macro"; +import { EVENT_WS_MESSAGE } from "../constants"; +import { MessageLevel } from "../elements/messages/Message"; +import { showMessage } from "../elements/messages/MessageContainer"; + +export interface WSMessage { + type: string; +} + +export class WebsocketClient { + + messageSocket?: WebSocket; + retryDelay = 200; + + constructor() { + try { + this.connect(); + } catch (error) { + console.warn(`authentik/ws: failed to connect to ws ${error}`); + } + } + + connect(): void { + if (navigator.webdriver) return; + const wsUrl = `${window.location.protocol.replace("http", "ws")}//${window.location.host + }/ws/client/`; + this.messageSocket = new WebSocket(wsUrl); + this.messageSocket.addEventListener("open", () => { + console.debug(`authentik/ws: connected to ${wsUrl}`); + this.retryDelay = 200; + }); + this.messageSocket.addEventListener("close", (e) => { + console.debug(`authentik/ws: closed ws connection: ${e}`); + if (this.retryDelay > 3000) { + showMessage({ + level: MessageLevel.error, + message: t`Connection error, reconnecting...` + }); + } + setTimeout(() => { + console.debug(`authentik/ws: reconnecting ws in ${this.retryDelay}ms`); + this.connect(); + }, this.retryDelay); + this.retryDelay = this.retryDelay * 2; + }); + this.messageSocket.addEventListener("message", (e) => { + const data = JSON.parse(e.data); + window.dispatchEvent( + new CustomEvent(EVENT_WS_MESSAGE, { + bubbles: true, + composed: true, + detail: data as WSMessage, + }) + ); + }); + this.messageSocket.addEventListener("error", () => { + this.retryDelay = this.retryDelay * 2; + }); + } + +} diff --git a/web/src/constants.ts b/web/src/constants.ts index 41c0d70ae..b3439b7c3 100644 --- a/web/src/constants.ts +++ b/web/src/constants.ts @@ -5,9 +5,14 @@ export const PROGRESS_CLASS = "pf-m-in-progress"; export const CURRENT_CLASS = "pf-m-current"; export const VERSION = "2021.7.1-rc1"; export const PAGE_SIZE = 20; +export const TITLE_DEFAULT = "authentik"; +export const ROUTE_SEPARATOR = ";"; + export const EVENT_REFRESH = "ak-refresh"; export const EVENT_NOTIFICATION_TOGGLE = "ak-notification-toggle"; export const EVENT_SIDEBAR_TOGGLE = "ak-sidebar-toggle"; export const EVENT_API_DRAWER_REFRESH = "ak-api-drawer-refresh"; -export const TITLE_DEFAULT = "authentik"; -export const ROUTE_SEPARATOR = ";"; +export const EVENT_WS_MESSAGE = "ak-ws-message"; + +export const WS_MSG_TYPE_MESSAGE = "message"; +export const WS_MSG_TYPE_REFRESH = "refresh"; diff --git a/web/src/elements/messages/MessageContainer.ts b/web/src/elements/messages/MessageContainer.ts index d0e1121d4..6cbc105c8 100644 --- a/web/src/elements/messages/MessageContainer.ts +++ b/web/src/elements/messages/MessageContainer.ts @@ -1,9 +1,10 @@ -import { t } from "@lingui/macro"; import { LitElement, html, customElement, TemplateResult, property, CSSResult, css } from "lit-element"; import "./Message"; -import { APIMessage, MessageLevel } from "./Message"; +import { APIMessage } from "./Message"; import PFAlertGroup from "@patternfly/patternfly/components/AlertGroup/alert-group.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; +import { EVENT_WS_MESSAGE, WS_MSG_TYPE_MESSAGE } from "../../constants"; +import { WSMessage } from "../../common/ws"; export function showMessage(message: APIMessage): void { const container = document.querySelector("ak-message-container"); @@ -20,9 +21,6 @@ export class MessageContainer extends LitElement { @property({attribute: false}) messages: APIMessage[] = []; - messageSocket?: WebSocket; - retryDelay = 200; - static get styles(): CSSResult[] { return [PFBase, PFAlertGroup, css` /* Fix spacing between messages */ @@ -34,11 +32,10 @@ export class MessageContainer extends LitElement { constructor() { super(); - try { - this.connect(); - } catch (error) { - console.warn(`authentik/messages: failed to connect to ws ${error}`); - } + this.addEventListener(EVENT_WS_MESSAGE, ((e: CustomEvent) => { + if (e.detail.type !== WS_MSG_TYPE_MESSAGE) return; + this.addMessage(e.detail as unknown as APIMessage); + }) as EventListener); } // add a new message, but only if the message isn't currently shown. @@ -49,40 +46,6 @@ export class MessageContainer extends LitElement { } } - connect(): void { - if (navigator.webdriver) return; - const wsUrl = `${window.location.protocol.replace("http", "ws")}//${ - window.location.host - }/ws/client/`; - this.messageSocket = new WebSocket(wsUrl); - this.messageSocket.addEventListener("open", () => { - console.debug(`authentik/messages: connected to ${wsUrl}`); - this.retryDelay = 200; - }); - this.messageSocket.addEventListener("close", (e) => { - console.debug(`authentik/messages: closed ws connection: ${e}`); - if (this.retryDelay > 3000) { - showMessage({ - level: MessageLevel.error, - message: t`Connection error, reconnecting...` - }); - } - setTimeout(() => { - console.debug(`authentik/messages: reconnecting ws in ${this.retryDelay}ms`); - this.connect(); - }, this.retryDelay); - this.retryDelay = this.retryDelay * 2; - }); - this.messageSocket.addEventListener("message", (e) => { - const data = JSON.parse(e.data); - this.addMessage(data); - this.requestUpdate(); - }); - this.messageSocket.addEventListener("error", () => { - this.retryDelay = this.retryDelay * 2; - }); - } - render(): TemplateResult { return html`