diff --git a/src/frontend/apps/web/app/stock/layout.tsx b/src/frontend/apps/web/app/stock/layout.tsx
index e9d10bb9..f3b9a17a 100644
--- a/src/frontend/apps/web/app/stock/layout.tsx
+++ b/src/frontend/apps/web/app/stock/layout.tsx
@@ -1,3 +1,4 @@
+import { ToastAlarm } from '@/src/features/alarm';
import AuthWrapper from '@/src/features/auth/ui/auth-wrapper';
import { getAccessToken } from '@/src/features/stock';
import { ProfilePopover } from '@/src/features/user';
@@ -8,6 +9,7 @@ import {
WebSocketProvider,
} from '@/src/shared';
import { getUserIdFromCookie } from '@/src/shared/services/lib';
+import { Toaster } from '@workspace/ui/components';
import '@workspace/ui/globals.css';
@@ -30,6 +32,8 @@ export default async function RootLayout({
{children}
+
+
>
diff --git a/src/frontend/apps/web/src/features/alarm/index.ts b/src/frontend/apps/web/src/features/alarm/index.ts
new file mode 100644
index 00000000..19996a78
--- /dev/null
+++ b/src/frontend/apps/web/src/features/alarm/index.ts
@@ -0,0 +1 @@
+export { ToastAlarm } from './model/toast-alarm';
diff --git a/src/frontend/apps/web/src/features/alarm/model/toast-alarm.tsx b/src/frontend/apps/web/src/features/alarm/model/toast-alarm.tsx
new file mode 100644
index 00000000..4005ac0d
--- /dev/null
+++ b/src/frontend/apps/web/src/features/alarm/model/toast-alarm.tsx
@@ -0,0 +1,33 @@
+'use client';
+
+import { useEffect } from 'react';
+import { webSocketEvent } from '@/src/shared/providers/stomp-websocket-provider';
+import { useToast } from '@workspace/ui/hooks/Toast/use-toast';
+import { ToastAction } from '@workspace/ui/components';
+
+export type Alarm = {
+ userNickname: string;
+ channelName: string;
+ text: string;
+};
+
+export const ToastAlarm = () => {
+ const { toast } = useToast();
+
+ useEffect(() => {
+ const handleMessage = (message: Alarm) => {
+ toast({
+ title: `${message.userNickname} #${message.channelName}`,
+ description: `${message.text}`,
+ });
+ };
+
+ webSocketEvent.on('alarmReceived', handleMessage);
+
+ return () => {
+ webSocketEvent.off('alarmReceived', handleMessage);
+ };
+ }, [toast]);
+
+ return null;
+};
diff --git a/src/frontend/apps/web/src/shared/providers/stomp-websocket-provider.tsx b/src/frontend/apps/web/src/shared/providers/stomp-websocket-provider.tsx
index 3e858d3e..9c20b290 100644
--- a/src/frontend/apps/web/src/shared/providers/stomp-websocket-provider.tsx
+++ b/src/frontend/apps/web/src/shared/providers/stomp-websocket-provider.tsx
@@ -12,10 +12,14 @@ import {
import * as StompJs from '@stomp/stompjs';
import SockJS from 'sockjs-client';
+import EventEmitter from 'events';
+
+export const webSocketEvent = new EventEmitter();
type WebSocketContextProps = {
client: StompJs.Client | null;
isConnected: boolean;
+ sessionId: string;
};
type WebSocketProviderProps = {
@@ -31,8 +35,7 @@ export const StompWebSocketProvider = ({
}: WebSocketProviderProps) => {
const client = useRef(null);
const [isConnected, setIsConnected] = useState(false);
-
- // const BASE_URL = `http://${process.env.NEXT_PUBLIC_BASE_URL}:${process.env.NEXT_PUBLIC_CHAT_SERVER1_PORT}`;
+ const sessionId = useRef(null);
const BASE_URL = `${process.env.NEXT_PUBLIC_REAL_BASE_URL}`;
const connect = useCallback(() => {
@@ -50,6 +53,23 @@ export const StompWebSocketProvider = ({
onConnect: () => {
console.log('✅ WebSocket Connected');
setIsConnected(true);
+
+ const socketUrl = client.current.webSocket['_transport'].ws.url;
+ sessionId.current = socketUrl.split('/').slice(-2, -1)[0];
+
+ client.current.subscribe(
+ `/subscribe/notification.${sessionId.current}`,
+ (message) => {
+ try {
+ const payload = JSON.parse(message.body);
+ console.log('📩 Received:', payload);
+
+ webSocketEvent.emit('alarmReceived', payload);
+ } catch (error) {
+ console.error('❌ 메시지 파싱 실패:', error);
+ }
+ },
+ );
},
onStompError: (frame) => {
console.error('❌ Broker error:', frame.headers['message'], frame.body);
@@ -73,7 +93,11 @@ export const StompWebSocketProvider = ({
return (
{children}