diff --git a/Cargo.toml b/Cargo.toml index 9ceee9a..3ac753b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,11 +28,13 @@ async-trait = "0.1" async-stream = "0.3" axum = "0.7" axum-extra = "0.9" +base64 = "0.22" cached = "0.47" cfg-if = "1" chrono = "0.4" compact_str = "0.7" dotenvy = "0.15" +ed25519-dalek = "2.1" either = "1.9" emojis = "0.6" futures = "0.3" @@ -42,6 +44,7 @@ headers = "0.4" http = "1.0" hyper = "1.1" idna = "0.5" +indexmap = "2.2" itertools = "0.12" lru = "0.12" miette = "5.9" @@ -51,12 +54,14 @@ percent-encoding = "2.2" quick-xml = "0.31" redis = "0.24" regex = "1.9" -reqwest = "0.11" +rsa = "0.9" +reqwest = "0.12" sea-orm = "0.12" sea-orm-migration = "0.12" serde = "1" serde_json = "1" serde_urlencoded = "0.7" +sha2 = "0.10" strum = "0.25" tera = { version = "1", default-features = false } thiserror = "1" diff --git a/fe_calckey/frontend/client/src/components/MagNotifications.vue b/fe_calckey/frontend/client/src/components/MagNotifications.vue index 92c9a10..4896ddb 100644 --- a/fe_calckey/frontend/client/src/components/MagNotifications.vue +++ b/fe_calckey/frontend/client/src/components/MagNotifications.vue @@ -5,6 +5,8 @@
+ +
+ + { }; let notifStream: ((e: ChannelEvent) => void) | undefined; + let connection: Connection; onMounted(() => { diff --git a/fe_calckey/frontend/client/src/components/MagStreamStatus.vue b/fe_calckey/frontend/client/src/components/MagStreamStatus.vue new file mode 100644 index 0000000..1163b80 --- /dev/null +++ b/fe_calckey/frontend/client/src/components/MagStreamStatus.vue @@ -0,0 +1,78 @@ + + + diff --git a/fe_calckey/frontend/locales/en-US.yml b/fe_calckey/frontend/locales/en-US.yml index 9069150..d8e1fb1 100644 --- a/fe_calckey/frontend/locales/en-US.yml +++ b/fe_calckey/frontend/locales/en-US.yml @@ -2199,3 +2199,8 @@ _experiments: _dialog: charactersExceeded: "Max characters exceeded! Current: {current}/Limit: {max}" charactersBelow: "Not enough characters! Current: {current}/Limit: {min}" + +connectionStatus: + connected: "Connected" + reconnecting: "Reconnecting" + disconnected: "Disconnected from the server" diff --git a/fe_calckey/frontend/magnetar-common/src/index.ts b/fe_calckey/frontend/magnetar-common/src/index.ts index 3d3b35c..c9103a8 100644 --- a/fe_calckey/frontend/magnetar-common/src/index.ts +++ b/fe_calckey/frontend/magnetar-common/src/index.ts @@ -13,7 +13,7 @@ import { FrontendApiEndpoints, } from "./fe-api"; -import { MagEventChannel } from "./sse-listener"; +import { MagEventChannel, MagChannelState } from "./sse-listener"; import * as types from "./types"; import * as packed from "./packed"; @@ -43,6 +43,7 @@ export { FrontendApiEndpoint, FrontendApiEndpoints, MagEventChannel, + MagChannelState, types, packed, endpoints, diff --git a/fe_calckey/frontend/magnetar-common/src/sse-listener.ts b/fe_calckey/frontend/magnetar-common/src/sse-listener.ts index db951c1..00c6fb8 100644 --- a/fe_calckey/frontend/magnetar-common/src/sse-listener.ts +++ b/fe_calckey/frontend/magnetar-common/src/sse-listener.ts @@ -1,7 +1,12 @@ import { EventEmitter } from "eventemitter3"; import { ChannelEvent } from "./types/ChannelEvent"; -export type MagChannelState = "connected" | "exponentialBackoff" | "failed"; +export type MagChannelState = + | "initial" + | "connected" + | "exponentialBackoff" + | "failed" + | "closed"; export class MagEventChannel extends EventEmitter<{ stateChange: MagChannelState; @@ -15,11 +20,12 @@ export class MagEventChannel extends EventEmitter<{ private readonly backoffFactor: number; private readonly backoffBase: number; private readonly closePromise: Promise<"cancelled">; + private _state: MagChannelState; public constructor( baseUrl: string, token: string | null, - maxReconnectAttempts: number = 12, + maxReconnectAttempts: number = 15, backoffFactor: number = 1.618, backoffBase: number = 500.0 ) { @@ -30,6 +36,8 @@ export class MagEventChannel extends EventEmitter<{ this.maxAttempts = maxReconnectAttempts; this.backoffFactor = backoffFactor; this.backoffBase = backoffBase; + this._state = "initial"; + this.updateState("initial"); this.closePromise = new Promise((resolve) => { this.on("close", resolve); }); @@ -49,9 +57,18 @@ export class MagEventChannel extends EventEmitter<{ return cb; } + public get state(): MagChannelState { + return this._state; + } + + private updateState(state: MagChannelState) { + this._state = state; + this.emit("stateChange", state); + } + private async connect() { if (this.attempts >= this.maxAttempts) { - this.emit("stateChange", "failed"); + this.updateState("failed"); return; } @@ -73,7 +90,7 @@ export class MagEventChannel extends EventEmitter<{ response.status >= 500 || response.body === null ) { - this.emit("stateChange", "exponentialBackoff"); + this.updateState("exponentialBackoff"); setTimeout( () => this.connect(), this.backoffBase * Math.pow(this.backoffFactor, this.attempts) @@ -83,7 +100,7 @@ export class MagEventChannel extends EventEmitter<{ } if (response.status >= 400 && response.status < 500) { - this.emit("stateChange", "failed"); + this.updateState("failed"); return; } @@ -91,7 +108,7 @@ export class MagEventChannel extends EventEmitter<{ const decoderStream = new TextDecoderStream(); const reader = response.body.pipeThrough(decoderStream).getReader(); - this.emit("stateChange", "connected"); + this.updateState("connected"); let buf = ""; @@ -101,11 +118,11 @@ export class MagEventChannel extends EventEmitter<{ if (res === "cancelled") break; if (res.done) { - this.emit("stateChange", "exponentialBackoff"); + this.updateState("exponentialBackoff"); setTimeout( () => this.connect(), this.backoffBase * - Math.pow(this.backoffFactor, this.attempts) + Math.pow(this.backoffFactor, this.attempts) ); this.attempts++; break; @@ -134,6 +151,8 @@ export class MagEventChannel extends EventEmitter<{ const data = JSON.parse(text) as ChannelEvent; this.emit("message", data); } + + this.updateState("closed"); } public async close() { diff --git a/fe_calckey/frontend/pnpm-lock.yaml b/fe_calckey/frontend/pnpm-lock.yaml index a5e9587..f1183b0 100644 --- a/fe_calckey/frontend/pnpm-lock.yaml +++ b/fe_calckey/frontend/pnpm-lock.yaml @@ -1438,8 +1438,8 @@ packages: '@types/node': 20.8.10 dev: true - /@types/yauzl@2.10.2: - resolution: {integrity: sha512-Km7XAtUIduROw7QPgvcft0lIupeG8a8rdKL8RiSyKvlE7dYY31fEn41HVuQsRFDuROA8tA4K2UVL+WdfFmErBA==} + /@types/yauzl@2.10.3: + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} requiresBuild: true dependencies: '@types/node': 14.18.63 @@ -2705,8 +2705,8 @@ packages: is-plain-object: 5.0.0 dev: true - /core-js@3.33.2: - resolution: {integrity: sha512-XeBzWI6QL3nJQiHmdzbAOiMYqjrb7hwU7A39Qhvd/POSa/t9E1AeZyEZx3fNvp/vtM8zXwhoL0FsiS0hD0pruQ==} + /core-js@3.36.1: + resolution: {integrity: sha512-BTvUrwxVBezj5SZ3f10ImnX2oRByMxql3EimVqMysepbC9EeMUOpLwdy6Eoili2x6E4kf+ZUB5k/+Jv55alPfA==} requiresBuild: true dev: true @@ -3428,7 +3428,7 @@ packages: get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: - '@types/yauzl': 2.10.2 + '@types/yauzl': 2.10.3 transitivePeerDependencies: - supports-color dev: true @@ -7733,7 +7733,7 @@ packages: name: plyr version: 3.7.0 dependencies: - core-js: 3.33.2 + core-js: 3.36.1 custom-event-polyfill: 1.0.7 loadjs: 4.2.0 rangetouch: 2.0.1