diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..614b191 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +# Ignore node_modules +node_modules + +# Ignore log files +*.log + +# Ignore build directory +build +dist + +# Ignore Dockerfile and .dockerignore itself +Dockerfile +.dockerignore + +# Ignore other files or directories that are not needed +.git +.gitignore diff --git a/.gitignore b/.gitignore index a3441c9..8f20321 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ node_modules # env -.env +.env* # build dist \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1e7c28a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +# FROM: 생성할 이미지의 베이스가 될 이미지 결정 +FROM node + +# WORKDIR: 이미지 내에서 명령어를 실행할 디렉토리를 설정 +# 컨테이너 내부에 /app 디렉토리를 생성하고, 이 디렉토리를 WORKDIR로 설정 +WORKDIR /app + +# COPY: 로컬 파일을 이미지 내부로 복사 +# package.json 파일을 /app 디렉토리로 복사 (.은 현재 디렉토리를 의미) +COPY package.json . +COPY package-lock.json . + +# RUN: 명령어를 실행 +# npm install 명령어를 실행하여 package.json에 명시된 패키지를 설치 +RUN npm install + +# COPY: 로컬 파일을 이미지 내부로 복사 +# 현재 디렉토리의 모든 파일을 /app 디렉토리로 복사 +COPY . . + +# EXPOSE: 컨테이너가 실행될 때 사용할 포트를 설정 +# 3000번 포트를 외부에 노출 +EXPOSE 8080 + +# CMD: 컨테이너가 시작되었을 때 실행할 명령어를 설정 +# npm start 명령어를 실행하여 서버를 실행 +CMD [ "npm", "start" ] \ No newline at end of file diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 0000000..b881b9b --- /dev/null +++ b/nodemon.json @@ -0,0 +1,6 @@ +{ + "watch": ["src/**/*"], + "ignore": ["src/public/*"], + "ext": "js,jsx,ts,tsx", + "exec": "npm start" +} diff --git a/package-lock.json b/package-lock.json index f4d29e2..c9b1dc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,14 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "express": "^4.19.2" + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.19.2", + "livekit-server-sdk": "^2.5.1", + "socket.io": "^4.7.5" }, "devDependencies": { + "@types/dotenv": "^8.2.0", "@types/express": "^4.17.21", "@types/node": "^20.14.10", "@typescript-eslint/eslint-plugin": "^7.15.0", @@ -24,6 +29,11 @@ "typescript": "^5.5.3" } }, + "node_modules/@bufbuild/protobuf": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz", + "integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==" + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -152,6 +162,14 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@livekit/protocol": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.19.1.tgz", + "integrity": "sha512-PQYIuqRv++fRik9tKulJ0C0tT5O4cNviBA7OxwLTCBFDxJpve8ua8/JZ+nK+7r4j2KbLfVjsJYop9wcTCgRn7Q==", + "dependencies": { + "@bufbuild/protobuf": "^1.7.2" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -187,6 +205,11 @@ "node": ">= 8" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -230,6 +253,29 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/dotenv": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-8.2.0.tgz", + "integrity": "sha512-ylSC9GhfRH7m1EUXBXofhgx4lUWmFeQDINW5oLuS+gxWdfUeW4zJdeVTYVkexEW+e2VUvlZR2kGnGGipAWR7kw==", + "deprecated": "This is a stub types definition. dotenv provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "dotenv": "*" + } + }, "node_modules/@types/express": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", @@ -270,7 +316,6 @@ "version": "20.14.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz", "integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==", - "dev": true, "dependencies": { "undici-types": "~5.26.4" } @@ -653,6 +698,14 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -758,6 +811,45 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-9.1.3.tgz", + "integrity": "sha512-Rircqi9ch8AnZscQcsA1C47NFdaO3wukpmIRzYcDOrmvgt78hM/sj5pZhZNec2NM12uk5vTwRHZ4anGcrC4ZTg==", + "dependencies": { + "camelcase": "^8.0.0", + "map-obj": "5.0.0", + "quick-lru": "^6.1.1", + "type-fest": "^4.3.2" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys/node_modules/type-fest": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.21.0.tgz", + "integrity": "sha512-ADn2w7hVPcK6w1I0uWnM//y1rLXZhzB9mr0a3OirzclKF1Wp6VzevUmzz/NRAWunOT6E8HrnpGY7xOfc6K57fA==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -875,6 +967,18 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -899,7 +1003,6 @@ "version": "4.3.5", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -984,6 +1087,17 @@ "node": ">=6.0.0" } }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -997,6 +1111,42 @@ "node": ">= 0.8" } }, + "node_modules/engine.io": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/es-define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", @@ -1741,6 +1891,14 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/jose": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.6.3.tgz", + "integrity": "sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -1793,6 +1951,19 @@ "node": ">= 0.8.0" } }, + "node_modules/livekit-server-sdk": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/livekit-server-sdk/-/livekit-server-sdk-2.5.1.tgz", + "integrity": "sha512-m6M5QIzfH7E1BvP1KX71DSMskekGDsVRNyVJIvuudESkriYqZW/suirMICMs8j4Zgkz0EdloykhrFL4FCeFkSA==", + "dependencies": { + "@livekit/protocol": "^1.19.0", + "camelcase-keys": "^9.0.0", + "jose": "^5.1.2" + }, + "engines": { + "node": ">=19" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -1820,6 +1991,17 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "node_modules/map-obj": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-5.0.0.tgz", + "integrity": "sha512-2L3MIgJynYrZ3TYMriLDLWocz15okFakV6J12HXvMXDHui2x/zgChzg1u9mFFGbbGWE+GsLpQByt4POb9Or+uA==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1908,8 +2090,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/natural-compare": { "version": "1.4.0", @@ -1962,6 +2143,14 @@ "node": ">=0.10.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", @@ -2198,6 +2387,17 @@ } ] }, + "node_modules/quick-lru": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.2.tgz", + "integrity": "sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -2461,6 +2661,44 @@ "node": ">=8" } }, + "node_modules/socket.io": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -2653,8 +2891,7 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/unpipe": { "version": "1.0.0", @@ -2725,6 +2962,26 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index f4e2dfb..c9b46e8 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "", "main": "index.js", + "type": "module", "scripts": { "build": "tsc", "start": "npm run build && NODE_ENV=production node ./dist/server.js", @@ -13,6 +14,7 @@ "author": "", "license": "ISC", "devDependencies": { + "@types/dotenv": "^8.2.0", "@types/express": "^4.17.21", "@types/node": "^20.14.10", "@typescript-eslint/eslint-plugin": "^7.15.0", @@ -25,6 +27,10 @@ "typescript": "^5.5.3" }, "dependencies": { - "express": "^4.19.2" + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.19.2", + "livekit-server-sdk": "^2.5.1", + "socket.io": "^4.7.5" } } diff --git a/src/server.ts b/src/server.ts index 0204dbf..33f0ba2 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,12 +1,71 @@ -import express, { Application, Request, Response } from "express"; +// server.js +import express from "express"; +import { + AccessToken, + AccessTokenOptions, + VideoGrant, +} from "livekit-server-sdk"; +import dotenv from "dotenv"; +import http from "http"; +import cors from "cors"; +import { Server } from "socket.io"; +import socketHandler from "./socket.js"; +dotenv.config(); -const app: Application = express(); -const PORT: number = 8080; +const createToken = async (userInfo: AccessTokenOptions, grant: VideoGrant) => { + const at = new AccessToken( + process.env.LIVEKIT_API_KEY, + process.env.LIVEKIT_API_SECRET, + userInfo + ); + at.addGrant(grant); -app.get("/", (req: Request, res: Response) => { - res.send("Hello World"); + return await at.toJwt(); +}; + +const app = express(); +app.use(cors()); +const PORT = 8080; + +const httpServer = http.createServer(app); +const wsServer = new Server(httpServer, { + cors: { + origin: [ + process.env.LOCALHOST_URL as string, + process.env.APP_URL as string, + ], + credentials: true, + }, +}); + +app.get("/", (req, res) => { + res.status(200).send("Hello, World!"); }); -app.listen(PORT, () => { - console.log(`Server is running on http://localhost:${PORT}`); +app.get("/getToken", async (req, res) => { + const { roomName, identity, name } = req.query; + if (!roomName || !identity || !name) { + return res.status(400).send("Missing required parameters"); + } + console.log(roomName, identity, name); + + const grant: VideoGrant = { + room: roomName as string, + roomJoin: true, + canPublish: true, + canPublishData: true, + canSubscribe: true, + }; + const token = await createToken( + { identity: identity as string, name: name as string }, + grant + ); + + res.status(200).json({ identity: identity, accessToken: token }); }); + +socketHandler(wsServer); + +httpServer.listen(PORT, () => + console.log(`HTTP Server is running on http://localhost:${PORT}`) +); diff --git a/src/socket.ts b/src/socket.ts new file mode 100644 index 0000000..128ba7d --- /dev/null +++ b/src/socket.ts @@ -0,0 +1,94 @@ +import { Server as SocketIo, Socket } from 'socket.io'; + +/* WebSocket */ +const totalRooms = {} as { [key: string]: { users: string[] } }; + +const socketHandler = (wsServer: SocketIo) => { + // Step 1: Connection + wsServer.on('connection', (socket: Socket) => { + console.log(`[Socket] Client connected: ${socket.id}`); + + // Step 2: Join Room + socket.on('join', (data: { room: string }) => { + if (!data.room) return; + + socket.join(data.room); + + // 방이 없으면 생성 + if (!totalRooms[data.room]) { + totalRooms[data.room] = { users: [] }; + } + + // 방에 사용자 추가 + totalRooms[data.room].users.push(socket.id); + console.log(`[Socket] ${socket.id} joined ${data.room}`); + }); + + // Step 3: Offer + socket.on('offer', (data: { room: string; sdp: string }) => { + socket.to(data.room).emit('offer', { sdp: data.sdp, sender: socket.id }); + console.log(`[Socket] Offer from ${socket.id} to ${data.room}`); + }); + + // Step 4: Answer + socket.on('answer', (data: { sdp: string; room: string }) => { + socket.to(data.room).emit('answer', { sdp: data.sdp, sender: socket.id }); + console.log(`[Socket] Answer from ${socket.id} to ${data.room}`); + }); + + // Step 5: Candidate + socket.on('candidate', (data: { candidate: string; room: string }) => { + socket.to(data.room).emit('candidate', { + candidate: data.candidate, + sender: socket.id, + }); + console.log(`[Socket] Candidate from ${socket.id} to ${data.room}`); + }); + + // Step 6: Leave + socket.on('disconnect', () => { + // 연결이 끊어지면 방에서 사용자 제거 + if (socket.rooms) { + Object.keys(socket.rooms).forEach((room) => { + if (totalRooms[room]) { + totalRooms[room].users = totalRooms[room].users.filter( + (userId: string) => userId !== socket.id, + ); + } + }); + } + + // 방에 사용자가 없으면 방 제거 + Object.keys(totalRooms).forEach((room) => { + if (totalRooms[room].users.length === 0) { + delete totalRooms[room]; + } + }); + + console.log(`[Socket] Client disconnected: ${socket.id}`); + }); + socket.on( + 'filter', + (data: { + room: string; + type: string; + x: number; + y: number; + width: number; + height: number; + }) => { + socket.to(data.room).emit('filter', { + type: data.type, + x: data.x, + y: data.y, + width: data.width, + height: data.height, + // sender: socket.id, + }); + console.log(`[Socket] Filter from ${socket.id} to ${data.room}`); + }, + ); + }); +}; + +export default socketHandler;