Skip to content

Commit

Permalink
Merge pull request #38 from PortalCube/dev
Browse files Browse the repository at this point in the history
[Feat/FE] WebRTC 페이지를 추가한다.
  • Loading branch information
PortalCube authored Nov 6, 2023
2 parents 5d447ed + d11ba88 commit 61dda18
Show file tree
Hide file tree
Showing 14 changed files with 612 additions and 13 deletions.
5 changes: 4 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@
VITE_SPRING_URL=https://example.com/api/

# AI 서비스의 URL을 입력합니다.
VITE_AI_URL=https://example.com/ai/
VITE_AI_URL=https://example.com/ai/

# 시그널링 서비스의 URL을 입력합니다.
VITE_SIGNALING_SERVICE_URL=ws://example.com/socket/
5 changes: 5 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import styled from "styled-components";
import "./App.scss";
import { ReducerContext } from "./reducer/context.js";
import ReservationListPage from "./pages/Reservation/ReservationListPage.jsx";
import ReservationMeetingPage from "./pages/Reservation/ReservationMeetingPage.jsx";

const Container = styled.div`
margin-top: 60px;
Expand Down Expand Up @@ -61,6 +62,10 @@ function App() {
/>
<Route path="/theramakeassgin" element={<TheraMakeAssignPage />} />
<Route path="/untact/list" element={<ReservationListPage />} />
<Route
path="/untact/meeting/:uuid"
element={<ReservationMeetingPage />}
/>
</Routes>
</Container>
</Router>
Expand Down
11 changes: 10 additions & 1 deletion src/components/Reservation/ReservationCreateModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
reserveCreateReducer,
} from "../../reducer/reservation-create.js";
import dayjs from "dayjs";
import { createReservation } from "../../librarys/api/reservation.js";

const Container = styled.div`
display: flex;
Expand Down Expand Up @@ -69,6 +70,7 @@ export const ReservationCreateModal = () => {
reserveCreateReducer,
intialReserveCreateState,
);

const [times, setTimes] = useState(createTimes());
const { index, available, description } = state;

Expand All @@ -93,7 +95,14 @@ export const ReservationCreateModal = () => {
});
}

function onComplete() {
async function onComplete() {
// const res = await createReservation(
// state.adminId,
// "ldh",
// state.description,
// [state.year, state.month + 1, state.date].join("-"),
// state.index,
// );
console.log(state);
}

Expand Down
11 changes: 9 additions & 2 deletions src/components/Reservation/ReservationItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import dayjs from "dayjs";
import classNames from "classnames";
import { useDispatch } from "react-redux";
import { show } from "../../redux/modalSlice.js";
import { useNavigate } from "react-router-dom";

const Container = styled.div`
height: 110px;
Expand Down Expand Up @@ -94,8 +95,9 @@ const dummyText = `그러나 한 시와 강아지, 가을 보고, 새워 까닭

const notReadyText = `아직 비대면 진료 요약이 생성되지 않았습니다.`;

const ReservationItem = ({ name, role, dept, date, index }) => {
const ReservationItem = ({ id, name, role, dept, date, index }) => {
const dispatch = useDispatch();
const navigate = useNavigate();

const image = useMemo(() => {
switch (role) {
Expand Down Expand Up @@ -130,7 +132,11 @@ const ReservationItem = ({ name, role, dept, date, index }) => {
if (isDone) {
return <Btn type="disabled">종료되었습니다</Btn>;
} else if (isOpen) {
return <Btn type="primary">입장</Btn>;
return (
<Btn type="primary" onClick={() => navigate("/untact/meeting/" + id)}>
입장
</Btn>
);
} else {
return <Btn type="disabled">예약 시간이 아닙니다</Btn>;
}
Expand Down Expand Up @@ -186,6 +192,7 @@ const ReservationItem = ({ name, role, dept, date, index }) => {
};

ReservationItem.propTypes = {
id: PropTypes.string,
name: PropTypes.string,
role: PropTypes.string,
date: PropTypes.string,
Expand Down
1 change: 1 addition & 0 deletions src/components/Reservation/ReservationList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const ReservationList = () => {
{list.map((item) => (
<ReservationItem
key={item.rno}
id={item.rno}
date={item.date}
index={item.index}
dept="한림대학교"
Expand Down
21 changes: 21 additions & 0 deletions src/librarys/api/reservation.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,24 @@ export async function getAdminReservationList(id, page = undefined) {
const response = await axios.get("/reservation-admin/" + id, { params });
return response.data;
}

export async function createReservation(
admin_id,
user_id,
content,
date,
index,
) {
const axios = getSpringAxios();

const data = {
admin_id,
user_id,
content,
date,
index,
};
console.log(data);
const response = await axios.post("/reservation/", data);
return response.data;
}
6 changes: 3 additions & 3 deletions src/librarys/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ export const CATEGORY_TYPE = {
THIGH: "허벅지",
};

export const ROLE_LIST = Object.entries(ROLE_TYPE).map((key, value) => ({
export const ROLE_LIST = Object.entries(ROLE_TYPE).map((key, value) => [
key,
value,
}));
]);

export const CATEGORY_LIST = Object.entries(CATEGORY_TYPE).map(
(key, value) => ({
([key, value]) => ({
key,
value,
}),
Expand Down
238 changes: 238 additions & 0 deletions src/librarys/webrtc/rtc-client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
import RTCSignalingClient from "./rtc-signaling.js";
import { registerEvents } from "./util.js";

const CONFIG = {
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
};

export class RTCClient extends EventTarget {
id = null;
role = null;

/** @type {RTCSignalingClient} */
signaling = null;

/** @type {RTCPeerConnection} */
peer = null;

/** @type {RTCDataChannel} */
dataChannel = null;

/** @type {MediaStream} */
clientStream = null;

/** @type {MediaStream} */
remoteStream = null;

get readyState() {
return this.peer.connectionState;
}

constructor() {
super();

const signalingEvents = {
offer: this._onOffer,
answer: this._onAnswer,
candidate: this._onCandidate,
disconnect: this._onDisconnect,
};

this.signaling = new RTCSignalingClient();
registerEvents(this.signaling, signalingEvents, this);

this.setRTCPeer();
}

async connect(id, role, stream) {
if (this.signaling.readyState) {
return;
}
this.id = id;
this.role = role;
this.setClientStream(stream);
await this.signaling.connect(this.id);
await this.call();
}

disconnect() {
if (this.dataChannel) {
this.dataChannel.close();
this.dataChannel = null;
}

this.peer.close();
this.signaling.send("disconnect", {});

this.setRTCPeer();
this.setClientStream(this.clientStream);
this.remoteStream = null;

this.dispatchEvent(new CustomEvent("disconnect"));
}

destory() {
if (this.dataChannel) {
this.dataChannel.close();
this.dataChannel = null;
}

this.peer.close();
this.signaling.send("disconnect", {});
this.signaling.disconnect();

this.clientStream.getTracks().forEach((track) => track.stop());

this.clientStream = null;
this.remoteStream = null;

this.dispatchEvent(new CustomEvent("disconnect"));
}

async call() {
if (!this.signaling.readyState) {
return;
}

this.log("Data channel을 만듭니다.");
this.setDataChannel(this.peer.createDataChannel("default"));

this.log("Offer를 보냅니다.");
const offer = await this.peer.createOffer();
await this.peer.setLocalDescription(offer);

this.signaling.send("offer", offer);
}

async answer() {
if (!this.signaling.readyState) {
return;
}

this.log("Answer를 보냅니다.");

const answer = await this.peer.createAnswer();
await this.peer.setLocalDescription(answer);

this.signaling.send("answer", answer);
}

sendMessage(message) {
if (this.dataChannel && this.dataChannel.readyState === "open") {
this.dataChannel.send(message);
} else {
throw new Error("[RTCClient] 아직 연결이 열리지 않았습니다.");
}
}

setRTCPeer() {
const peerEvents = {
connectionstatechange: this._onConnectionStateChange,
icecandidate: this._onIceCandidate,
datachannel: this._onDataChannel,
track: this._onTrack,
};

this.peer = new RTCPeerConnection(CONFIG);
registerEvents(this.peer, peerEvents, this);
}

async setRemoteDescription(payload) {
const remoteDescription = new RTCSessionDescription(payload);
await this.peer.setRemoteDescription(remoteDescription);
}

setDataChannel(channel) {
this.dataChannel = channel;
this.dataChannel.addEventListener("open", (event) => {
this.log("data open", event);
});
this.dataChannel.addEventListener("message", (event) => {
this.log("data message", event);
});
}

setClientStream(stream) {
this.clientStream = stream;
this.clientStream.getTracks().forEach((track) => {
this.peer.addTrack(track, this.clientStream);
});
}

// Peer Events
_onIceCandidate(event) {
const candidate = event.candidate;
if (candidate) {
this.log("Candidate 정보를 전달합니다.");
this.signaling.send("candidate", candidate);
}
}

_onConnectionStateChange(event) {
this.log("연결 상태가 변경되었습니다:", this.peer.connectionState);
if (["disconnected", "failed"].includes(this.peer.connectionState)) {
this.disconnect();
} else if (this.peer.connectionState === "connected") {
this.dispatchEvent(new CustomEvent("open"));
}
}

_onDataChannel(event) {
if (event.channel) {
this.log("Channel 데이터를 받았습니다:", event.channel);
this.setDataChannel(event.channel);
this.dispatchEvent(new CustomEvent("channelopen"));
}
}

_onTrack(event) {
if (event.streams) {
this.log("MediaStream을 받았습니다:", event.streams);
this.remoteStream = event.streams[0];
this.dispatchEvent(
new CustomEvent("stream", { detail: this.remoteStream }),
);
}
}

// Data Channel Events
_onChannelOpen(event) {}
_onChannelMessage(event) {}

// Signaling Events
async _onOffer({ detail: payload }) {
this.log("Offer 정보를 받았습니다.");
// Remote Description 지정
await this.setRemoteDescription(payload);
await this.answer();
}

async _onAnswer({ detail: payload }) {
this.log("Answer 정보를 받았습니다.");
// Remote Description 지정
await this.setRemoteDescription(payload);
}

async _onCandidate({ detail: payload }) {
this.log("Candidate 정보를 받았습니다.", event);
try {
const candidate = new RTCIceCandidate(payload);
await this.peer.addIceCandidate(candidate);
} catch (e) {
this.logError("ice candidate를 받는데 실패했습니다.", e);
}
}

_onDisconnect({ detail: payload }) {
this.log("상대가 연결을 종료했습니다.");
this.disconnect();
}

log(...arg) {
console.log("[RTCClient]", ...arg);
}

logError(...arg) {
console.error("[RTCClient]", ...arg);
}
}
Loading

0 comments on commit 61dda18

Please sign in to comment.