Basic WS sync
This commit is contained in:
@@ -1,9 +1,54 @@
|
|||||||
import { APIClient } from "./ApiClient";
|
import { APIClient } from "./ApiClient";
|
||||||
|
|
||||||
export type WsMessage = {
|
interface BaseRoomEvent {
|
||||||
type: string;
|
time: number;
|
||||||
[k: string]: any;
|
room_id: string;
|
||||||
|
event_id: string;
|
||||||
|
sender: string;
|
||||||
|
origin_server_ts: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageType = "m.text" | "m.image" | string;
|
||||||
|
|
||||||
|
export interface RoomMessageEvent extends BaseRoomEvent {
|
||||||
|
type: "RoomMessageEvent";
|
||||||
|
data: {
|
||||||
|
msgtype: MessageType;
|
||||||
|
body: string;
|
||||||
|
"m.relates_to"?: {
|
||||||
|
rel_type?: "m.replace" | string;
|
||||||
|
event_id?: string;
|
||||||
};
|
};
|
||||||
|
"m.new_content"?: {
|
||||||
|
msgtype?: MessageType;
|
||||||
|
body?: string;
|
||||||
|
};
|
||||||
|
file?: { url: string };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RoomReactionEvent extends BaseRoomEvent {
|
||||||
|
type: "RoomReactionEvent";
|
||||||
|
data: {
|
||||||
|
"m.relates_to": {
|
||||||
|
rel_type: string;
|
||||||
|
event_id: string;
|
||||||
|
key: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RoomRedactionEvent extends BaseRoomEvent {
|
||||||
|
type: "RoomRedactionEvent";
|
||||||
|
data: {
|
||||||
|
redacts: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WsMessage =
|
||||||
|
| RoomMessageEvent
|
||||||
|
| RoomReactionEvent
|
||||||
|
| RoomRedactionEvent;
|
||||||
|
|
||||||
export class WsApi {
|
export class WsApi {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import type {
|
import type {
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
|
MatrixEventData,
|
||||||
MatrixEventsList,
|
MatrixEventsList,
|
||||||
} from "../api/matrix/MatrixApiEvent";
|
} from "../api/matrix/MatrixApiEvent";
|
||||||
import type { Room } from "../api/matrix/MatrixApiRoom";
|
import type { Room } from "../api/matrix/MatrixApiRoom";
|
||||||
|
import type { WsMessage } from "../api/WsApi";
|
||||||
|
|
||||||
export interface MessageReaction {
|
export interface MessageReaction {
|
||||||
event_id: string;
|
event_id: string;
|
||||||
@@ -45,7 +47,62 @@ export class RoomEventsManager {
|
|||||||
this.rebuildMessagesList();
|
this.rebuildMessagesList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
processWsMessage(m: WsMessage) {
|
||||||
|
if (m.room_id !== this.room.id) return false;
|
||||||
|
|
||||||
|
let data: MatrixEventData;
|
||||||
|
if (m.type === "RoomReactionEvent") {
|
||||||
|
data = {
|
||||||
|
type: "m.reaction",
|
||||||
|
content: {
|
||||||
|
"m.relates_to": {
|
||||||
|
key: m.data["m.relates_to"].key,
|
||||||
|
event_id: m.data["m.relates_to"].event_id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (m.type === "RoomRedactionEvent") {
|
||||||
|
data = {
|
||||||
|
type: "m.room.redaction",
|
||||||
|
redacts: m.data.redacts,
|
||||||
|
};
|
||||||
|
} else if (m.type === "RoomMessageEvent") {
|
||||||
|
data = {
|
||||||
|
type: "m.room.message",
|
||||||
|
content: {
|
||||||
|
body: m.data["m.new_content"]?.body ?? m.data.body,
|
||||||
|
msgtype: m.data.msgtype,
|
||||||
|
"m.relates_to":
|
||||||
|
m.data["m.relates_to"] && m.data["m.relates_to"].event_id
|
||||||
|
? {
|
||||||
|
event_id: m.data["m.relates_to"].event_id!,
|
||||||
|
rel_type: m.data["m.relates_to"].rel_type ?? "",
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
file: m.data.file,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Ignore event
|
||||||
|
console.info("Event not supported => ignored");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.events.push({
|
||||||
|
sender: m.sender,
|
||||||
|
id: m.event_id,
|
||||||
|
time: m.origin_server_ts,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.rebuildMessagesList();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private rebuildMessagesList() {
|
private rebuildMessagesList() {
|
||||||
|
this.messages = [];
|
||||||
|
|
||||||
// Sorts events list to process oldest events first
|
// Sorts events list to process oldest events first
|
||||||
this.events.sort((a, b) => a.time - b.time);
|
this.events.sort((a, b) => a.time - b.time);
|
||||||
|
|
||||||
|
|||||||
64
matrixgw_frontend/src/widgets/messages/MatrixWS.tsx
Normal file
64
matrixgw_frontend/src/widgets/messages/MatrixWS.tsx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { WsApi, type WsMessage } from "../../api/WsApi";
|
||||||
|
import { useSnackbar } from "../../hooks/contexts_provider/SnackbarProvider";
|
||||||
|
import CircleIcon from "@mui/icons-material/Circle";
|
||||||
|
import { Tooltip } from "@mui/material";
|
||||||
|
|
||||||
|
const State = {
|
||||||
|
Closed: "Closed",
|
||||||
|
Connected: "Connected",
|
||||||
|
Error: "Error",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export function MatrixWS(p: {
|
||||||
|
onMessage: (msg: WsMessage) => void;
|
||||||
|
}): React.ReactElement {
|
||||||
|
const snackbar = useSnackbar();
|
||||||
|
|
||||||
|
const [state, setState] = React.useState<string>(State.Closed);
|
||||||
|
const wsRef = React.useRef<WebSocket | undefined>(undefined);
|
||||||
|
const [connCount, setConnCount] = React.useState(0);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const count = connCount;
|
||||||
|
const ws = new WebSocket(WsApi.WsURL);
|
||||||
|
wsRef.current = ws;
|
||||||
|
|
||||||
|
ws.onopen = () => setState(State.Connected);
|
||||||
|
ws.onerror = (e) => {
|
||||||
|
if (count != connCount) return;
|
||||||
|
|
||||||
|
console.error(`WS Debug error!`, e);
|
||||||
|
snackbar(`WebSocket error!`);
|
||||||
|
setState(State.Error);
|
||||||
|
|
||||||
|
setTimeout(() => setConnCount(connCount + 1), 500);
|
||||||
|
};
|
||||||
|
ws.onclose = () => {
|
||||||
|
setState(State.Closed);
|
||||||
|
wsRef.current = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onmessage = (msg) => {
|
||||||
|
const dec = JSON.parse(msg.data);
|
||||||
|
console.info("WS message", dec);
|
||||||
|
p.onMessage(dec);
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => ws.close();
|
||||||
|
}, [connCount]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip title={state}>
|
||||||
|
<CircleIcon
|
||||||
|
color={
|
||||||
|
state === State.Connected
|
||||||
|
? "success"
|
||||||
|
: state === State.Error
|
||||||
|
? "error"
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,8 +2,10 @@ import React from "react";
|
|||||||
import { MatrixApiEvent } from "../../api/matrix/MatrixApiEvent";
|
import { MatrixApiEvent } from "../../api/matrix/MatrixApiEvent";
|
||||||
import type { UsersMap } from "../../api/matrix/MatrixApiProfile";
|
import type { UsersMap } from "../../api/matrix/MatrixApiProfile";
|
||||||
import type { Room } from "../../api/matrix/MatrixApiRoom";
|
import type { Room } from "../../api/matrix/MatrixApiRoom";
|
||||||
|
import type { WsMessage } from "../../api/WsApi";
|
||||||
import { RoomEventsManager } from "../../utils/RoomEventsManager";
|
import { RoomEventsManager } from "../../utils/RoomEventsManager";
|
||||||
import { AsyncWidget } from "../AsyncWidget";
|
import { AsyncWidget } from "../AsyncWidget";
|
||||||
|
import { MatrixWS } from "./MatrixWS";
|
||||||
import { RoomMessagesList } from "./RoomMessagesList";
|
import { RoomMessagesList } from "./RoomMessagesList";
|
||||||
import { SendMessageForm } from "./SendMessageForm";
|
import { SendMessageForm } from "./SendMessageForm";
|
||||||
|
|
||||||
@@ -11,6 +13,7 @@ export function RoomWidget(p: {
|
|||||||
room: Room;
|
room: Room;
|
||||||
users: UsersMap;
|
users: UsersMap;
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
|
const [_count, setCount] = React.useState(0);
|
||||||
const [roomMgr, setRoomMgr] = React.useState<undefined | RoomEventsManager>();
|
const [roomMgr, setRoomMgr] = React.useState<undefined | RoomEventsManager>();
|
||||||
|
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
@@ -20,6 +23,10 @@ export function RoomWidget(p: {
|
|||||||
setRoomMgr(mgr);
|
setRoomMgr(mgr);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleNewMessage = (m: WsMessage) => {
|
||||||
|
if (roomMgr?.processWsMessage(m)) setCount((c) => c + 1);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncWidget
|
<AsyncWidget
|
||||||
loadKey={p.room.id}
|
loadKey={p.room.id}
|
||||||
@@ -28,6 +35,9 @@ export function RoomWidget(p: {
|
|||||||
errMsg="Failed to load room!"
|
errMsg="Failed to load room!"
|
||||||
build={() => (
|
build={() => (
|
||||||
<div style={{ display: "flex", flexDirection: "column", flex: 1 }}>
|
<div style={{ display: "flex", flexDirection: "column", flex: 1 }}>
|
||||||
|
<div style={{ position: "absolute", right: "0px", padding: "10px" }}>
|
||||||
|
<MatrixWS onMessage={handleNewMessage} />
|
||||||
|
</div>
|
||||||
<RoomMessagesList mgr={roomMgr!} {...p} />
|
<RoomMessagesList mgr={roomMgr!} {...p} />
|
||||||
<SendMessageForm {...p} />
|
<SendMessageForm {...p} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user