
{
"name": "hourglass_planner_front",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"lint": "next lint",
"local": "node server-local.js",
"start": "next start -p 3000"
},
"dependencies": {
"chart.js": "^4.4.3",
"dotenv": "^16.4.5",
"env-cmd": "^10.1.0",
"http-proxy-middleware": "^3.0.0",
"js-cookie": "^3.0.5",
"next": "14.2.4",
"react": "^18",
"react-dom": "^18",
"simple-peer": "^9.11.1",
"socket.io-client": "^4.7.5",
"sockjs-client": "^1.6.1",
"uuid": "^10.0.0",
"webstomp-client": "^1.2.6",
"yarn": "^1.22.22",
"zustand": "^4.5.4"
},
"devDependencies": {
"@types/js-cookie": "^3.0.6",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/simple-peer": "^9.11.8",
"@types/sockjs-client": "^1.5.4",
"@types/uuid": "^10.0.0",
"eslint": "^8",
"eslint-config-next": "14.2.4",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}
const { createServer } = require('https');
const { parse } = require('url');
const next = require('next');
const fs = require('fs');
const path = require('path');
require('dotenv').config({ path: '.env.localhost' }); // 환경 변수 로드
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
// 인증서 파일 경로 설정
const httpsOptions = {
key: fs.readFileSync(path.resolve(__dirname, 'dev.key')),
cert: fs.readFileSync(path.resolve(__dirname, 'dev.cert')),
};
const port = process.env.PORT || 3000; // 로컬에서는 3000 포트 사용
app.prepare().then(() => {
const server = createServer(httpsOptions, (req, res) => {
res.setHeader('Access-Control-Allow-Origin', 'https://localhost:3000');
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Allow-Credentials', 'true');
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
const parsedUrl = parse(req.url, true);
handle(req, res, parsedUrl);
});
server.listen(port, (err) => {
if (err) throw err;
console.log(`> Ready on https://localhost:${port}/`);
});
});
숫자를 입력하고 방 생성 버튼을 누르면 세션으로 이동하게 된다.
'use client';
import { useState } from "react";
export default function TestPage() {
const [room, setRoom] = useState("");
const [rooms, setRooms] = useState<string[]>([]);
const [socket, setSocket] = useState<WebSocket | null>(null);
const createRoom = () => {
if (room) {
setRooms([...rooms, room]);
const ws = new WebSocket('wss://localhost:8080/signal');
ws.onopen = () => {
console.log('WebSocket connection established');
ws.send(JSON.stringify({ type: "create_room", room }));
};
ws.onmessage = (message) => {
console.log("Message from server: ", message.data);
};
ws.onerror = (error) => {
console.error('WebSocket error: ', error);
};
ws.onclose = (event) => {
console.log('WebSocket connection closed', event);
};
setSocket(ws);
}
};
const joinRoom = (room: string) => {
console.log("Joining room: ", room);
if (socket) {
socket.send(JSON.stringify({ type: "join_room", room }));
}
};
return (
<div className="flex flex-col items-center">
<div className="mb-4">
<input
type="text"
value={room}
onChange={(e) => setRoom(e.target.value)}
placeholder="Enter room number"
className="border p-2"
/>
<button onClick={createRoom} className="ml-2 p-2 bg-blue-500 text-white">
Create Room
</button>
</div>
<div>
{rooms.map((room, index) => (
<button
key={index}
onClick={() => joinRoom(room)}
className="m-2 p-2 bg-green-500 text-white"
>
Join Room {room}
</button>
))}
</div>
</div>
);
}
npm install socket.io-client
const { createServer } = require('https');
const { parse } = require('url');
const next = require('next');
const fs = require('fs');
const path = require('path');
const socketIo = require('socket.io');
require('dotenv').config({ path: '.env.localhost' }); // 환경 변수 로드
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
// 인증서 파일 경로 설정
const httpsOptions = {
key: fs.readFileSync(path.resolve(__dirname, 'dev.key')),
cert: fs.readFileSync(path.resolve(__dirname, 'dev.cert')),
};
const port = process.env.PORT || 3000; // 로컬에서는 3000 포트 사용
app.prepare().then(() => {
const server = createServer(httpsOptions, (req, res) => {
res.setHeader('Access-Control-Allow-Origin', 'https://localhost:3000');
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Allow-Credentials', 'true');
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
const parsedUrl = parse(req.url, true);
handle(req, res, parsedUrl);
});
const io = socketIo(server, {
cors: {
origin: "https://localhost:3000",
methods: ["GET", "POST"],
credentials: true
}
});
io.on('connection', (socket) => {
console.log('New client connected:', socket.id);
socket.on('create_room', (data) => {
const room = data.room;
socket.join(room);
console.log(`Room ${room} created by client ${socket.id}`);
io.to(room).emit('room_created', { room });
});
socket.on('join_room', (data) => {
const room = data.room;
socket.join(room);
console.log(`Client ${socket.id} joined room ${room}`);
io.to(room).emit('room_joined', { room });
});
socket.on('disconnect', () => {
console.log('Client disconnected:', socket.id);
});
});
server.listen(port, (err) => {
if (err) throw err;
console.log(`> Ready on https://localhost:${port}/`);
});
});
'use client';
import { useState, useEffect } from "react";
import io from 'socket.io-client';
const socket = io('https://localhost:3000', {
withCredentials: true,
transports: ['websocket'],
});
export default function TestPage() {
const [room, setRoom] = useState("");
const [rooms, setRooms] = useState<string[]>([]);
useEffect(() => {
socket.on('room_created', (data) => {
console.log('Room created:', data.room);
});
socket.on('room_joined', (data) => {
console.log('Room joined:', data.room);
});
return () => {
socket.off('room_created');
socket.off('room_joined');
};
}, []);
const createRoom = () => {
if (room) {
setRooms([...rooms, room]);
socket.emit('create_room', { room });
}
};
const joinRoom = (room: string) => {
console.log("Joining room: ", room);
socket.emit('join_room', { room });
};
return (
<div className="flex flex-col items-center">
<div className="mb-4">
<input
type="text"
value={room}
onChange={(e) => setRoom(e.target.value)}
placeholder="Enter room number"
className="border p-2"
/>
<button onClick={createRoom} className="ml-2 p-2 bg-blue-500 text-white">
Create Room
</button>
</div>
<div>
{rooms.map((room, index) => (
<button
key={index}
onClick={() => joinRoom(room)}
className="m-2 p-2 bg-green-500 text-white"
>
Join Room {room}
</button>
))}
</div>
</div>
);
}
const { createServer } = require('https');
const { parse } = require('url');
const next = require('next');
const fs = require('fs');
const path = require('path');
const socketIo = require('socket.io');
require('dotenv').config({ path: '.env.localhost' }); // 환경 변수 로드
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
// 인증서 파일 경로 설정
const httpsOptions = {
key: fs.readFileSync(path.resolve(__dirname, 'dev.key')),
cert: fs.readFileSync(path.resolve(__dirname, 'dev.cert')),
};
const port = process.env.PORT || 3000; // 로컬에서는 3000 포트 사용
app.prepare().then(() => {
const server = createServer(httpsOptions, (req, res) => {
res.setHeader('Access-Control-Allow-Origin', 'https://localhost:3000');
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Allow-Credentials', 'true');
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
const parsedUrl = parse(req.url, true);
handle(req, res, parsedUrl);
});
const io = socketIo(server, {
cors: {
origin: "https://localhost:3000",
methods: ["GET", "POST"],
credentials: true
}
});
io.on('connection', (socket) => {
console.log('New client connected:', socket.id);
socket.on('create_room', (data) => {
const room = data.room;
socket.join(room);
console.log(`Room ${room} created by client ${socket.id}`);
io.to(room).emit('room_created', { room });
});
socket.on('join_room', (data) => {
const room = data.room;
socket.join(room);
console.log(`Client ${socket.id} joined room ${room}`);
io.to(room).emit('room_joined', { room });
});
socket.on('signal', (data) => {
const { room, signalData } = data;
socket.to(room).emit('signal', { signalData, id: socket.id });
});
socket.on('disconnect', () => {
console.log('Client disconnected:', socket.id);
});
});
server.listen(port, (err) => {
if (err) throw err;
console.log(`> Ready on https://localhost:${port}/`);
});
});
'use client';
import { useState, useEffect, useRef } from "react";
import io from 'socket.io-client';
const socket = io('https://localhost:3000', {
withCredentials: true,
transports: ['websocket'],
});
export default function TestPage() {
const [room, setRoom] = useState("");
const [rooms, setRooms] = useState<string[]>([]);
const localVideoRef = useRef<HTMLVideoElement>(null);
const remoteVideoRef = useRef<HTMLVideoElement>(null);
const peerConnectionRef = useRef<RTCPeerConnection | null>(null);
useEffect(() => {
socket.on('room_created', (data) => {
console.log('Room created:', data.room);
});
socket.on('room_joined', (data) => {
console.log('Room joined:', data.room);
});
socket.on('signal', async (data) => {
if (peerConnectionRef.current) {
try {
await peerConnectionRef.current.setRemoteDescription(new RTCSessionDescription(data.signalData));
if (data.signalData.type === 'offer') {
const answer = await peerConnectionRef.current.createAnswer();
await peerConnectionRef.current.setLocalDescription(answer);
socket.emit('signal', { room, signalData: answer });
}
} catch (error) {
console.error('Error handling signal:', error);
}
}
});
return () => {
socket.off('room_created');
socket.off('room_joined');
socket.off('signal');
};
}, []);
const createRoom = () => {
if (room) {
setRooms([...rooms, room]);
socket.emit('create_room', { room });
initializePeerConnection();
}
};
const joinRoom = (room: string) => {
console.log("Joining room: ", room);
socket.emit('join_room', { room });
initializePeerConnection();
};
const initializePeerConnection = async () => {
const peerConnection = new RTCPeerConnection({
iceServers: [
{
urls: 'stun:stun.l.google.com:19302',
},
],
});
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
socket.emit('signal', { room, signalData: event.candidate });
}
};
peerConnection.ontrack = (event) => {
if (remoteVideoRef.current) {
remoteVideoRef.current.srcObject = event.streams[0];
}
};
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
stream.getTracks().forEach((track) => peerConnection.addTrack(track, stream));
if (localVideoRef.current) {
localVideoRef.current.srcObject = stream;
}
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
socket.emit('signal', { room, signalData: offer });
} catch (error) {
console.error('Error accessing media devices:', error);
}
peerConnectionRef.current = peerConnection;
};
return (
<div className="flex flex-col items-center">
<div className="mb-4">
<input
type="text"
value={room}
onChange={(e) => setRoom(e.target.value)}
placeholder="Enter room number"
className="border p-2"
/>
<button onClick={createRoom} className="ml-2 p-2 bg-blue-500 text-white">
Create Room
</button>
</div>
<div>
{rooms.map((room, index) => (
<button
key={index}
onClick={() => joinRoom(room)}
className="m-2 p-2 bg-green-500 text-white"
>
Join Room {room}
</button>
))}
</div>
<div className="video-container">
<video ref={localVideoRef} autoPlay muted className="local-video" />
<video ref={remoteVideoRef} autoPlay className="remote-video" />
</div>
</div>
);
}