diff --git a/src/components/AppBar.tsx b/src/components/AppBar.tsx
index f4e3dfe..92f544d 100644
--- a/src/components/AppBar.tsx
+++ b/src/components/AppBar.tsx
@@ -29,22 +29,22 @@ export const AppBar = () => {
}
- onClick={() => navigate('/', { replace: true })}
+ onClick={() => navigate('/')}
/>
}
- onClick={() => navigate('/fav-players', { replace: true })}
+ onClick={() => navigate('/fav-players')}
/>
}
- onClick={() => navigate('/find', { replace: true })}
+ onClick={() => navigate('/find')}
/>
}
- onClick={() => navigate('/user-center', { replace: true })}
+ onClick={() => navigate('/user-center')}
/>
diff --git a/src/components/EventCard.tsx b/src/components/EventCard.tsx
index 2422d51..352ef17 100644
--- a/src/components/EventCard.tsx
+++ b/src/components/EventCard.tsx
@@ -3,10 +3,15 @@ import type { IEventInfo } from "../types";
import dayjs from "dayjs";
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
-import { EyeOutlined } from "@ant-design/icons";
-import { useCallback, useEffect, useMemo, useState } from "react";
+import { BellFilled, BellOutlined, EyeOutlined } from "@ant-design/icons";
+import { useCallback, useContext, useEffect, useMemo, useState } from "react";
import { useNavigate } from "react-router";
import type { TimerType } from "antd/lib/statistic/Timer";
+import { useLogto } from "@logto/react";
+import { useAuthHeaders } from "../hooks/useAuthHeaders";
+import { useRequest } from "ahooks";
+import { WebScoketContext } from "../context/WebsocketContext";
+import { getEventSubKey } from "../utils/common";
dayjs.extend(utc);
dayjs.extend(timezone);
@@ -61,6 +66,43 @@ export function EventCard(props: EventCardProps) {
setStatisticType(statistic.type);
// console.debug('format: %s', day.format(statistic.format), statistic);
}, [getStatisticProps])
+ const { isAuthenticated } = useLogto();
+ const { messageSender } = useContext(WebScoketContext);
+ const headers = useAuthHeaders();
+ const isSubscried = useRequest(async () => {
+ return fetch(`/api/subscribe-event/${e.matchId}`, {
+ headers,
+ })
+ .then(res => res.json())
+ .then(json => json.isSub);
+ }, { refreshDeps: [headers], debounceWait: 300 })
+ const subReq = useRequest(async () => {
+ return fetch(`/api/subscribe-event/${e.matchId}`, {
+ headers,
+ method: 'PUT',
+ });
+ }, { manual: true, refreshDeps: [e, headers], debounceWait: 300 });
+ const unSubReq = useRequest(async () => {
+ return fetch(`/api/subscribe-event/${e.matchId}`, {
+ headers,
+ method: 'DELETE',
+ });
+ }, { manual: true, refreshDeps: [e, headers], debounceWait: 300 });
+ const [subLoading, setSubloading] = useState(false);
+ const handleSub = useCallback(async () => {
+ setSubloading(true);
+ await subReq.runAsync();
+ await isSubscried.runAsync();
+ messageSender?.('SUB', { topic: getEventSubKey(e.matchId) });
+ setSubloading(false);
+ }, [e, subReq, isSubscried]);
+ const handleUnSub = useCallback(async () => {
+ setSubloading(true);
+ await unSubReq.runAsync();
+ await isSubscried.runAsync();
+ messageSender?.('UNSUB', { topic: getEventSubKey(e.matchId) });
+ setSubloading(false);
+ }, [e, isSubscried, unSubReq]);
useEffect(() => {
const timeout = day.toDate().getTime() - Date.now();
updateMessageFormat();
@@ -69,12 +111,27 @@ export function EventCard(props: EventCardProps) {
}, timeout);
return () => clearTimeout(id);
}, [updateMessageFormat]);
+ const isSubBtnDisabled = useMemo(() => {
+ if (!isAuthenticated) return true;
+ if (subLoading) return true;
+ if (e.isFinished && !isSubscried.data) return true;
+ return false;
+ }, [isAuthenticated, subLoading, isSubscried]);
return (
:}
+ // onClick={isSubscried.data ? handleUnSub : handleSub}
+ // >
+ // {isSubscried.data ? '取消提醒' : '提醒我'}
+ // ,
+
diff --git a/src/components/HydrateFallback.tsx b/src/components/HydrateFallback.tsx
index 1c4144f..2089670 100644
--- a/src/components/HydrateFallback.tsx
+++ b/src/components/HydrateFallback.tsx
@@ -1,4 +1,4 @@
-import { Button, Flex, Spin } from "antd";
+import { Button, Flex, Spin, Typography } from "antd";
import { useEffect, useState } from "react";
export function HydrateFallback() {
@@ -13,7 +13,7 @@ export function HydrateFallback() {
{tooLongTime && (
- window.location.reload()}>等的太久了,刷新试试?
+ 桥豆麻袋,正在努力加载数据中....
)}
);
diff --git a/src/components/Layout/AppBarLayout.tsx b/src/components/Layout/AppBarLayout.tsx
index ce2862b..8409b37 100644
--- a/src/components/Layout/AppBarLayout.tsx
+++ b/src/components/Layout/AppBarLayout.tsx
@@ -2,6 +2,9 @@ import { Outlet, useNavigation } from "react-router";
import { HydrateFallback } from "../HydrateFallback";
import { AppBar } from "../AppBar";
import styled from "styled-components";
+import { useAuthSocket } from "../../hooks/useAuthSocket";
+import { WebScoketContext } from "../../context/WebsocketContext";
+import { Alert, Button } from "antd";
const StyledContainer = styled.div`
padding-bottom: 90px;
@@ -10,8 +13,13 @@ const StyledContainer = styled.div`
export const AppBarLayout = () => {
const navigation = useNavigation();
const loading = navigation.state === 'loading';
+ const [sender] = useAuthSocket();
return loading ? : (
-
-
+
+
+
+
+
+
);
}
\ No newline at end of file
diff --git a/src/components/WebSocketHandler/Handler.tsx b/src/components/WebSocketHandler/Handler.tsx
deleted file mode 100644
index e69de29..0000000
diff --git a/src/components/WebSocketHandler/useAuthSocket.ts b/src/components/WebSocketHandler/useAuthSocket.ts
deleted file mode 100644
index d63f5c0..0000000
--- a/src/components/WebSocketHandler/useAuthSocket.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { useLogto } from "@logto/react";
-import { useRef } from "react"
-import { useRequest } from "ahooks";
-import { LOGTO_RESOURCE } from "../../utils/constants";
-
-export const useAuthSocket = () => {
- const wsRef = useRef(null);
- const { isAuthenticated, getAccessToken } = useLogto();
- const initWs = useRequest(async () => {
- if (!isAuthenticated) return;
- if (wsRef.current) {
- if (wsRef.current.readyState === WebSocket.OPEN) {
- return wsRef.current;
- }
- }
- const token = await getAccessToken(LOGTO_RESOURCE);
- const url = `${window.origin}/ws?token=${token}`.replace(/^http/, 'ws');
- const ws = new WebSocket(url);
- wsRef.current = ws;
- return ws;
- }, { manual: false, refreshDeps: [isAuthenticated, getAccessToken], debounceWait: 300 });
- return initWs;
-}
\ No newline at end of file
diff --git a/src/context/WebsocketContext.tsx b/src/context/WebsocketContext.tsx
new file mode 100644
index 0000000..60105b8
--- /dev/null
+++ b/src/context/WebsocketContext.tsx
@@ -0,0 +1,8 @@
+import { createContext } from "react";
+import type { WsWebSendTopicPayload, WsWebSendTopics } from "../utils/common";
+
+interface MessageSender {
+ messageSender?: (topic: T, data: WsWebSendTopicPayload[T]) => void;
+};
+
+export const WebScoketContext = createContext({});
\ No newline at end of file
diff --git a/src/hooks/useAuthSocket.ts b/src/hooks/useAuthSocket.ts
new file mode 100644
index 0000000..607bbfd
--- /dev/null
+++ b/src/hooks/useAuthSocket.ts
@@ -0,0 +1,35 @@
+import { useLogto } from "@logto/react";
+import { useCallback, useEffect, useState } from "react"
+import { useWebSocket } from "ahooks";
+import { EVENT_WS_MESSAGE, LOGTO_RESOURCE } from "../utils/constants";
+import { fromServerMessage, toWebProcessMessage, type WsWebSendTopicPayload, type WsWebSendTopics } from "../utils/common";
+
+
+function getWSURL(token: string) {
+ if (!token) return '';
+ return `${window.origin}/ws?token=${token}`.replace(/^http/, 'ws');
+}
+
+export const useAuthSocket = () => {
+ const { isAuthenticated, getAccessToken } = useLogto();
+ const [token, setToken] = useState('');
+ useEffect(() => {
+ if (!isAuthenticated) return;
+ getAccessToken(LOGTO_RESOURCE)
+ .then(token => setToken(token ?? ''));
+ }, [isAuthenticated]);
+ const result = useWebSocket(getWSURL(token), {
+ reconnectLimit: 3,
+ onMessage(message, instance) {
+ const event = new CustomEvent(EVENT_WS_MESSAGE, { detail: {
+ message: message.data,
+ instance,
+ } });
+ window.dispatchEvent(event);
+ },
+ });
+ const sender = useCallback((topic: T, data: WsWebSendTopicPayload[T]) => {
+ result.sendMessage(toWebProcessMessage(topic, data));
+ }, [result]);
+ return [sender];
+}
\ No newline at end of file
diff --git a/src/hooks/useHandlerServerMessage.tsx b/src/hooks/useHandlerServerMessage.tsx
new file mode 100644
index 0000000..4403555
--- /dev/null
+++ b/src/hooks/useHandlerServerMessage.tsx
@@ -0,0 +1,63 @@
+import { useCallback, useEffect } from "react";
+import { ensureTopicData, fromServerMessage } from "../utils/common";
+import { EVENT_WS_MESSAGE } from "../utils/constants";
+import { App } from "antd";
+
+export const useServerMessageHandler = () => {
+ const { message, notification } = App.useApp();
+ const processCustomEvent = useCallback(async (msg: string, ws: WebSocket) => {
+ const { topic, data } = fromServerMessage(msg);
+ console.debug('Handle ws message, topic: %s', topic, data);
+ switch (topic) {
+ case "MEMBER_CHANGE": {
+ message.info({
+ key: 'MEMBER_CHANGE',
+ content: `Online members: ${data}`,
+ });
+ break;
+ }
+ case "MY_CLIENT_ONLINE": {
+ message.info({
+ key: 'MY_CLIENT_ONLINE',
+ content: `New client online, clients: ${data}`,
+ });
+ break;
+ }
+ case "DEBUG_MSG":
+ console.debug('DEBUG_MSG', data);
+ break;
+ case "EVENT_MEMBER_CHANGE":
+ case "MSG": {
+ const msgData = ensureTopicData<'MSG'>(data);
+ const hasPermission = await Notification.requestPermission();
+ if (hasPermission === 'granted') {
+ const options = {
+ body: `有新的消息`,
+ icon: "https://example.com/icon.png", // 通知图标
+ badge: "https://example.com/badge.png", // 移动端状态栏图标
+ tag: `MSG`, // 相同 tag 的通知会覆盖,防止刷屏
+ renotify: true // 覆盖旧通知时是否再次振动/提醒
+ };
+ new Notification("新消息提醒", options);
+ return;
+ }
+ notification.info({
+ key: 'MSG',
+ title: 'You have a new message',
+ description: `from: ${msgData?.name}`
+ });
+ break;
+ }
+ default:
+ break;
+ }
+ }, []);
+ useEffect(() => {
+ const handler = (e: Event) => {
+ const { message, instance } = (e as CustomEvent).detail;
+ processCustomEvent(message, instance);
+ };
+ window.addEventListener(EVENT_WS_MESSAGE, handler);
+ return () => window.removeEventListener(EVENT_WS_MESSAGE, handler);
+ }, [processCustomEvent]);
+};
\ No newline at end of file
diff --git a/src/index.tsx b/src/index.tsx
index 8ba5b6d..cd90654 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -23,6 +23,29 @@ const server = Bun.serve({
routes: {
// Serve index.html for all unmatched routes.
"/*": index,
+ "/sw.js": async () => {
+ const build = await Bun.build({
+ entrypoints: ["./src/sw.ts"],
+ target: "browser",
+ // 如果你需要压缩,可以开启
+ minify: process.env.NODE_ENV === "production",
+ });
+
+ if (!build.success) {
+ return new Response("Build Error", { status: 500 });
+ }
+
+ // 读取编译后的第一个输出文件(即 sw.js)
+ const blob = build.outputs[0];
+
+ return new Response(blob, {
+ headers: {
+ "Content-Type": "application/javascript",
+ // 开发环境下禁用缓存,确保 SW 能及时更新
+ "Cache-Control": "no-cache",
+ },
+ });
+ },
"/api/club/find": {
async GET(req) {
const searchParams = new URL(req.url).searchParams;
@@ -327,7 +350,11 @@ const server = Bun.serve({
WebSocketService.addConnection(ws);
},
message(ws, message) {
- WebSocketService.processMessage(ws, message);
+ try {
+ WebSocketService.processMessage(ws, message);
+ } catch(e) {
+ console.debug('Parse message error', e, message.toString());
+ }
},
close(ws, code, reason) {
console.debug('close ws', code, reason)
diff --git a/src/routes.tsx b/src/routes.tsx
index 150df80..afefee2 100644
--- a/src/routes.tsx
+++ b/src/routes.tsx
@@ -55,8 +55,8 @@ export const route = createBrowserRouter([
const uidScore = await fetch(`/api/user/nowScores`, {
method: "POST",
body: JSON.stringify({ uids }),
- });
- return { info, members, uidScore: new Map(Object.entries(await uidScore.json())) };
+ }).then(res => res.json()).catch(() => ({}));
+ return { info, members, uidScore: new Map(Object.entries(uidScore)) };
},
Component: EventPage,
HydrateFallback: () =>
diff --git a/src/services/WebsocketService.ts b/src/services/WebsocketService.ts
index 1acebe4..1b827c1 100644
--- a/src/services/WebsocketService.ts
+++ b/src/services/WebsocketService.ts
@@ -1,9 +1,13 @@
-import { fromCustomMessage, toCustomMessage, WSTopic } from "../utils/common";
+import { ensureTopicData, fromCustomMessage, getEventSubKey, toWebProcessMessage, type WsServerSendTopicPayload, type WsServerSendTopics, type WsTopicPayload } from "../utils/common";
import { EventSubscribeService } from "./EventSubscribeService";
import type { WsPaylaod } from "../types";
-const publicTopics = [
- WSTopic.MEMBER_CHANGE,
+const publicTopics: WsServerSendTopics[] = [
+ 'MEMBER_CHANGE',
+ 'MY_CLIENT_ONLINE',
+ 'DEBUG_MSG',
+ 'EVENT_MEMBER_CHANGE',
+ "MSG",
];
type BunServerWebSocket = Bun.ServerWebSocket;
@@ -23,24 +27,27 @@ export class WebSocketService {
}
this.#userClients.get(user.sub)?.add(ws);
await this.#initSubscribe(ws);
- const message = toCustomMessage(WSTopic.MEMBER_CHANGE, this.#userClients.size);
if (isNewMember) {
- this.broadcast(WSTopic.MEMBER_CHANGE, message);
- } else {
- ws.send(message);
+ this.broadcast('MEMBER_CHANGE', this.#userClients.size ?? 0);
}
this.userClientsBroadcast(
ws.data.user.sub,
- WSTopic.MY_CLIENT_ONLINE,
- toCustomMessage(WSTopic.MY_CLIENT_ONLINE, this.#userClients.get(user.sub)?.size),
+ 'MY_CLIENT_ONLINE',
+ this.#userClients.get(user.sub)?.size ?? 0,
+ );
+ this.userClientsBroadcast(
+ ws.data.user.sub,
+ 'DEBUG_MSG',
+ `SubscribeKeys:\n${[...(this.#userSubTopics.get(ws.data.user.sub) ?? [])].join('|')}`,
);
}
static async #initSubscribe(ws: BunServerWebSocket) {
- const user = ws.data.user
- const subEvets = await EventSubscribeService.getEvents(user.sub).then(e => e.map(v => `event:${v}`));
+ const user = ws.data.user;
+ const subEvets = await EventSubscribeService.getEvents(user.sub).then(e => e.map(v => getEventSubKey(v)));
this.userSub(ws, user.sub, publicTopics);
this.userSub(ws, user.sub, subEvets);
+ this.userSub(ws, user.sub, [`MSG:${user.sub}`]);
}
static publish(ws: BunServerWebSocket, topic: string, message: string, withSelf?: boolean) {
@@ -50,8 +57,11 @@ export class WebSocketService {
}
}
- static async userClientsBroadcast(user: string, topic: string, message: string) {
- this.#userClients.get(user)?.forEach(ws => ws.send(message));
+ static async userClientsBroadcast(user: string, topic: T, data: WsServerSendTopicPayload[T]) {
+ this.#userClients.get(user)?.forEach(ws => {
+ if (!ws.isSubscribed(topic)) return;
+ ws.send(JSON.stringify({ topic, data }));
+ });
}
static removeConnection(ws: BunServerWebSocket) {
@@ -60,12 +70,12 @@ export class WebSocketService {
this.#userClients.get(ws.data.user.sub)?.delete(ws);
if (this.#userClients.get(ws.data.user.sub)?.size === 0) {
this.#userClients.delete(ws.data.user.sub);
- this.publish(ws, WSTopic.MEMBER_CHANGE, toCustomMessage(WSTopic.MEMBER_CHANGE, this.#userClients.size));
+ this.publish(ws, 'MEMBER_CHANGE', toWebProcessMessage('MEMBER_CHANGE', this.#userClients.size));
}
this.userClientsBroadcast(
ws.data.user.sub,
- WSTopic.MY_CLIENT_ONLINE,
- toCustomMessage(WSTopic.MY_CLIENT_ONLINE, this.#userClients.get(ws.data.user.sub)?.size),
+ 'MY_CLIENT_ONLINE',
+ this.#userClients.get(ws.data.user.sub)?.size ?? 0,
);
}
@@ -74,17 +84,33 @@ export class WebSocketService {
this.#userSubTopics.set(user, new Set());
}
if (!topics.length) return;
- console.debug('User %s subscribe keys: %s', user, topics.join(','));
topics.forEach(topic => {
this.#userSubTopics.get(user)?.add(topic);
ws.subscribe(topic);
});
}
- static broadcast(topic: string, message: string) {
- this.#connections.values().forEach(con => {
+ static userNotificate(user: string, topic: T, data: WsTopicPayload[T]) {
+ const connections = this.#userClients.get(user);
+ if (!connections?.size) return;
+ connections.forEach(con => {
if (con.isSubscribed(topic)) {
- con.send(message);
+ con.sendText(toWebProcessMessage(topic, data));
+ }
+ });
+ }
+
+ static broadcast(topic: T, data: WsTopicPayload[T], checkTopic?: T | string) {
+ console.debug('broadcast', { topic, data });
+ this.#connections.values().forEach((con, i) => {
+ const isSubscribed = con.isSubscribed(topic || checkTopic);
+ console.debug(`CheckTopicSubscribed`, {
+ [topic]: con.isSubscribed(topic),
+ ...checkTopic ? { [checkTopic]: con.isSubscribed(checkTopic) } : {},
+ });
+ if (isSubscribed) {
+ console.debug('Send broadcast to [%s]: %s', i + 1, con.data.user.sub, { topic, data });
+ con.send(toWebProcessMessage(topic, data));
}
});
}
@@ -94,17 +120,34 @@ export class WebSocketService {
if (!action) return;
console.debug('Recieve action: %s', action, message);
switch (action) {
- case WSTopic.SUB:
- ws.subscribe(data.topic);
+ case 'SUB': {
+ const topic = ensureTopicData<'SUB'>(data)?.topic ?? '';
+ ws.subscribe(topic);
+ if (!this.#userSubTopics.get(ws.data.user.sub)) {
+ this.#userSubTopics.set(ws.data.user.sub, new Set());
+ }
+ this.#userSubTopics.get(ws.data.user.sub)?.add(topic);
+ console.debug('Subs', this.#userSubTopics.get(ws.data.user.sub));
+ this.userClientsBroadcast(ws.data.user.sub, 'DEBUG_MSG', `Some client subscribed to: ${topic}`);
break;
- case WSTopic.UNSUB:
- ws.unsubscribe(data.topic);
+ }
+ case 'UNSUB': {
+ const topic = ensureTopicData<'UNSUB'>(data)?.topic ?? '';
+ ws.unsubscribe(topic);
+ if (!this.#userSubTopics.get(ws.data.user.sub)) {
+ this.#userSubTopics.set(ws.data.user.sub, new Set());
+ }
+ this.#userSubTopics.get(ws.data.user.sub)?.delete(topic);
+ console.debug('Subs', this.#userSubTopics.get(ws.data.user.sub));
break;
- case WSTopic.SEND:
- ws.publish(data.topic, data.message);
+ }
+ case 'MSG': {
+ const msgData = ensureTopicData<'MSG'>(data);
+ if (!msgData) return;
+ this.broadcast('MSG', msgData, `MSG:${msgData.channel}`);
break;
+ }
default:
- this.broadcast("Test", 'This is a broadcast message. Everyone should recieved.');
break;
}
}
diff --git a/src/sw.ts b/src/sw.ts
new file mode 100644
index 0000000..e357c47
--- /dev/null
+++ b/src/sw.ts
@@ -0,0 +1,42 @@
+///
+
+const sw = self as unknown as ServiceWorkerGlobalScope;
+
+sw.addEventListener('install', (event) => {
+ console.log('SW Installed');
+ sw.skipWaiting();
+});
+
+// 核心:处理通知推送
+sw.addEventListener('push', (event) => {
+ const data = event.data?.json() ?? {};
+
+ event.waitUntil(
+ sw.registration.showNotification(data.title || '新通知', {
+ body: data.body || '您有一条新消息',
+ icon: '/pwa-192x192.png',
+ // 携带自定义数据,方便点击时跳转
+ data: { url: data.url || '/' }
+ })
+ );
+});
+
+// 处理通知点击跳转
+sw.addEventListener('notificationclick', (event) => {
+ event.notification.close();
+ const urlToOpen = event.notification.data.url;
+
+ event.waitUntil(
+ sw.clients.matchAll({ type: 'window' }).then((windowClients) => {
+ // 如果已经打开了页面,则聚焦;否则打开新窗口
+ for (const client of windowClients) {
+ if (client.url === urlToOpen && 'focus' in client) {
+ return client.focus();
+ }
+ }
+ if (sw.clients.openWindow) {
+ return sw.clients.openWindow(urlToOpen);
+ }
+ })
+ );
+});
\ No newline at end of file
diff --git a/src/utils/common.ts b/src/utils/common.ts
index 9fbbc33..b86b8ea 100644
--- a/src/utils/common.ts
+++ b/src/utils/common.ts
@@ -87,29 +87,72 @@ export function calculate(winerScore: number, loserScore: number) {
}
}
-export function toCustomMessage(clientTopic: string | number, data: any) {
- return JSON.stringify({ topic: clientTopic, data });
+export type WsServerSendTopicPayload = {
+ MEMBER_CHANGE: number;
+ MY_CLIENT_ONLINE: number;
+ DEBUG_MSG: string;
+ EVENT_MEMBER_CHANGE: {
+ event: string;
+ memberNum: number;
+ };
+ MSG: { channel: string; name: string; avatar: string; message: string };
}
-export enum WSTopic {
- UNKONOW = 'UNKONW',
- MY_CLIENT_ONLINE = 'CLIENT_ONLINE',
- MEMBER_CHANGE = 'MEMBER_CHNAGE',
- SUB = "SUB",
- UNSUB = "UNSUB",
- SEND = "PUBLISH",
+export type WsServerSendTopics = keyof WsServerSendTopicPayload;
+
+export type WsWebSendTopicPayload = {
+ UNKONOW: undefined;
+ SUB: { topic: string };
+ UNSUB: { topic: string };
+ MSG: { channel: string; name: string; avatar: string; message: string };
+}
+
+export type WsWebSendTopics = keyof WsWebSendTopicPayload;
+
+export type WsTopicPayload = WsServerSendTopicPayload & WsWebSendTopicPayload;
+export type WsTopics = keyof WsTopicPayload;
+
+export function ensureTopicData(data: any): WsTopicPayload[T] | undefined {
+ return data;
+}
+
+export function getEventSubKey(eventId: string) {
+ return `event:${eventId}`;
+}
+
+export function toWebProcessMessage(
+ topic: WsTopics,
+ data: WsTopicPayload[WsTopics],
+) {
+ return JSON.stringify({ topic, data });
}
export function fromCustomMessage(message: string): {
- clientTopic: WSTopic;
- data?: any;
+ clientTopic: WsWebSendTopics;
+ data?: WsWebSendTopicPayload[WsWebSendTopics];
} {
try {
const { topic: clientTopic, data } = JSON.parse(message);
return { clientTopic, data };
} catch(e) {
return {
- clientTopic: WSTopic.UNKONOW,
+ clientTopic: 'UNKONOW',
+ data: undefined,
};
}
}
+
+export function fromServerMessage(message: string): {
+ topic: WsServerSendTopics;
+ data?: WsServerSendTopicPayload[WsServerSendTopics];
+} {
+ try {
+ const { topic, data } = JSON.parse(message);
+ return { topic, data };
+ } catch(e) {
+ return {
+ topic: 'DEBUG_MSG',
+ data: `${e}`,
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index c66197c..16e2ac2 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -1,4 +1,5 @@
export const LOGTO_RESOURCE = 'https://tt.ksr.la';
export const CLUB_SELECTOR_KEY = 'CLUB_SELECTOR';
export const STORE_PAGE_LIST_KEY = 'events-page-keys';
-export const MATCH_RESULT_MAP_KEY = 'match-result-map';
\ No newline at end of file
+export const MATCH_RESULT_MAP_KEY = 'match-result-map';
+export const EVENT_WS_MESSAGE = 'EVENT_WS_MESSAGE';
\ No newline at end of file