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 @@
+
+
+
+ {{ i18n.t("connectionStatus.connected") }}
+
+
+ {{ i18n.t("connectionStatus.reconnecting") }}
+
+
+
+ {{ i18n.t("connectionStatus.disconnected") }}
+
+
+
+
+
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