From 4f57377a9067baa66fca3639b63e4f1a7e1bb112 Mon Sep 17 00:00:00 2001 From: zero0205 Date: Wed, 22 Jan 2025 00:45:48 +0900 Subject: [PATCH 1/8] =?UTF-8?q?:wrench:=20[Chore]:=20sonarqube-scanner=20?= =?UTF-8?q?=EC=84=A4=EC=B9=98=20=EB=B0=8F=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/.gitignore | 4 + apps/client/package.json | 4 +- apps/client/sonar-project.properties | 3 + pnpm-lock.yaml | 166 +++++++++++++++++++++++++-- 4 files changed, 167 insertions(+), 10 deletions(-) create mode 100644 apps/client/sonar-project.properties diff --git a/apps/client/.gitignore b/apps/client/.gitignore index a547bf36..5bc5b2cb 100644 --- a/apps/client/.gitignore +++ b/apps/client/.gitignore @@ -22,3 +22,7 @@ dist-ssr *.njsproj *.sln *.sw? + +# Sonar +.sonar/ +.scannerwork/ \ No newline at end of file diff --git a/apps/client/package.json b/apps/client/package.json index 8dc66bf8..7c4e3e1d 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -7,7 +7,8 @@ "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint", - "preview": "vite preview" + "preview": "vite preview", + "sonar": "sonar-scanner" }, "dependencies": { "@radix-ui/react-avatar": "^1.1.1", @@ -49,6 +50,7 @@ "eslint-plugin-react-refresh": "^0.4.14", "postcss": "^8.4.47", "prettier": "*", + "sonarqube-scanner": "^4.2.6", "tailwindcss": "^3.4.14", "typescript": "*", "vite": "^5.4.10" diff --git a/apps/client/sonar-project.properties b/apps/client/sonar-project.properties new file mode 100644 index 00000000..34fbe225 --- /dev/null +++ b/apps/client/sonar-project.properties @@ -0,0 +1,3 @@ +sonar.projectKey=CamOn +sonar.sources=. +sonar.host.url=http://localhost:9000 \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cf6ea675..3ad0663d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -396,6 +396,9 @@ importers: prettier: specifier: '*' version: 2.8.8 + sonarqube-scanner: + specifier: ^4.2.6 + version: 4.2.6 tailwindcss: specifier: ^3.4.14 version: 3.4.14(ts-node@10.9.2(@swc/core@1.8.0)(@types/node@20.17.6)(typescript@5.6.3)) @@ -3070,6 +3073,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + adm-zip@0.5.12: + resolution: {integrity: sha512-6TVU49mK6KZb4qG6xWaaM4C7sA/sgUMLy/JYMOzkcp3BvVLpW0fXDFQiIzAuxFCt/2+xD7fNIiPFAoLZPhVNLQ==} + engines: {node: '>=6.0'} + ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -3265,6 +3272,9 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} + b4a@1.6.7: + resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} + babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3308,6 +3318,9 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + bare-events@2.5.4: + resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -3564,6 +3577,10 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + commander@12.0.0: + resolution: {integrity: sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==} + engines: {node: '>=18'} + commander@12.1.0: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} @@ -4170,6 +4187,9 @@ packages: fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -4314,6 +4334,10 @@ packages: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} + fs-extra@11.2.0: + resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} + engines: {node: '>=14.14'} + fs-extra@9.1.0: resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} engines: {node: '>=10'} @@ -4488,6 +4512,10 @@ packages: resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} engines: {node: '>=0.10.0'} + hpagent@1.2.0: + resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==} + engines: {node: '>=14'} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -4900,6 +4928,10 @@ packages: resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-sonar-reporter@2.0.0: + resolution: {integrity: sha512-ZervDCgEX5gdUbdtWsjdipLN3bKJwpxbvhkYNXTAYvAckCihobSLr9OT/IuyNIRT1EZMDDwR6DroWtrq+IL64w==} + engines: {node: '>=8.0.0'} + jest-util@29.7.0: resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -5137,6 +5169,10 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + lru-cache@7.18.3: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} @@ -5359,6 +5395,10 @@ packages: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + node-forge@1.3.1: + resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} + engines: {node: '>= 6.13.0'} + node-gyp-build@4.8.3: resolution: {integrity: sha512-EMS95CMJzdoSKoIiXo8pxKoL8DYxwIZXYlLmgPb8KUv794abpnLK6ynsCAWNliOjREKruYKdzbh76HHYUHX7nw==} hasBin: true @@ -5686,6 +5726,9 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + properties-file@3.5.4: + resolution: {integrity: sha512-OGQPWZ4j9ENDKBl+wUHqNtzayGF5sLlVcmjcqEMUUHeCbUSggDndii+kjcBDPj3GQvqYB9sUEc4siX36wx4glw==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -5710,6 +5753,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + queue-tick@1.0.1: + resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} + randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -5989,6 +6035,11 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true + semver@7.6.0: + resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} + engines: {node: '>=10'} + hasBin: true + semver@7.6.3: resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} engines: {node: '>=10'} @@ -6064,6 +6115,10 @@ packages: resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} engines: {node: '>=18'} + slugify@1.6.6: + resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==} + engines: {node: '>=8.0.0'} + socket.io-adapter@2.5.5: resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==} @@ -6083,6 +6138,11 @@ packages: resolution: {integrity: sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==} engines: {node: '>=10.2.0'} + sonarqube-scanner@4.2.6: + resolution: {integrity: sha512-UK6mCGr290bKo6yML9fYOyLrvPkU7vmnYPLvTWVUIQpxiTbkPm4bmPvhcIcSBBH0dN+cKObcrne1E8zuEYl95g==} + engines: {node: '>= 18'} + hasBin: true + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -6126,6 +6186,9 @@ packages: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} + streamx@2.21.1: + resolution: {integrity: sha512-PhP9wUnFLa+91CPy3N6tiQsK+gnYyUNuk15S3YG/zjYE7RuPeCjJngqnzpC31ow0lzBHQ+QGO4cNJnd0djYUsw==} + string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -6274,6 +6337,9 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + tar@7.4.3: resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} engines: {node: '>=18'} @@ -6307,6 +6373,9 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} + text-decoder@1.2.3: + resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + text-hex@1.0.0: resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} @@ -6795,6 +6864,9 @@ packages: utf-8-validate: optional: true + xml@1.0.1: + resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==} + xmlhttprequest-ssl@2.1.2: resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==} engines: {node: '>=0.4.0'} @@ -8269,7 +8341,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7(supports-color@5.5.0) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -8337,7 +8409,7 @@ snapshots: '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7(supports-color@5.5.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -9945,7 +10017,7 @@ snapshots: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3) '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7(supports-color@5.5.0) eslint: 8.57.1 optionalDependencies: typescript: 5.6.3 @@ -9978,7 +10050,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3) '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.6.3) - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7(supports-color@5.5.0) eslint: 8.57.1 ts-api-utils: 1.4.0(typescript@5.6.3) optionalDependencies: @@ -10009,7 +10081,7 @@ snapshots: dependencies: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.5 @@ -10163,6 +10235,8 @@ snapshots: acorn@8.14.0: {} + adm-zip@0.5.12: {} + ajv-formats@2.1.1(ajv@8.12.0): optionalDependencies: ajv: 8.12.0 @@ -10366,6 +10440,8 @@ snapshots: axobject-query@4.1.0: {} + b4a@1.6.7: {} + babel-jest@29.7.0(@babel/core@7.26.0): dependencies: '@babel/core': 7.26.0 @@ -10447,6 +10523,9 @@ snapshots: balanced-match@1.0.2: {} + bare-events@2.5.4: + optional: true + base64-js@1.5.1: {} base64id@2.0.0: {} @@ -10717,6 +10796,8 @@ snapshots: dependencies: delayed-stream: 1.0.0 + commander@12.0.0: {} + commander@12.1.0: {} commander@2.20.3: {} @@ -11037,7 +11118,7 @@ snapshots: engine.io-client@6.6.2(bufferutil@4.0.8)(utf-8-validate@5.0.10): dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7(supports-color@5.5.0) engine.io-parser: 5.2.3 ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) xmlhttprequest-ssl: 2.1.2 @@ -11372,7 +11453,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -11523,6 +11604,8 @@ snapshots: fast-diff@1.3.0: {} + fast-fifo@1.3.2: {} + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -11693,6 +11776,12 @@ snapshots: jsonfile: 6.1.0 universalify: 2.0.1 + fs-extra@11.2.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + fs-extra@9.1.0: dependencies: at-least-node: 1.0.0 @@ -11866,6 +11955,8 @@ snapshots: dependencies: parse-passwd: 1.0.0 + hpagent@1.2.0: {} + html-escaper@2.0.2: {} http-errors@2.0.0: @@ -12528,6 +12619,10 @@ snapshots: transitivePeerDependencies: - supports-color + jest-sonar-reporter@2.0.0: + dependencies: + xml: 1.0.1 + jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 @@ -12801,6 +12896,10 @@ snapshots: dependencies: yallist: 3.1.1 + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + lru-cache@7.18.3: {} lru.min@1.1.1: {} @@ -13000,6 +13099,8 @@ snapshots: fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 + node-forge@1.3.1: {} + node-gyp-build@4.8.3: optional: true @@ -13309,6 +13410,8 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + properties-file@3.5.4: {} + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -13328,6 +13431,8 @@ snapshots: queue-microtask@1.2.3: {} + queue-tick@1.0.1: {} + randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 @@ -13642,6 +13747,10 @@ snapshots: semver@6.3.1: {} + semver@7.6.0: + dependencies: + lru-cache: 6.0.0 + semver@7.6.3: {} send@0.19.0: @@ -13739,6 +13848,8 @@ snapshots: ansi-styles: 6.2.1 is-fullwidth-code-point: 5.0.0 + slugify@1.6.6: {} + socket.io-adapter@2.5.5(bufferutil@4.0.8)(utf-8-validate@5.0.10): dependencies: debug: 4.3.7(supports-color@5.5.0) @@ -13751,7 +13862,7 @@ snapshots: socket.io-client@4.8.1(bufferutil@4.0.8)(utf-8-validate@5.0.10): dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7(supports-color@5.5.0) engine.io-client: 6.6.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) socket.io-parser: 4.2.4 transitivePeerDependencies: @@ -13762,7 +13873,7 @@ snapshots: socket.io-parser@4.2.4: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -13794,6 +13905,23 @@ snapshots: - supports-color - utf-8-validate + sonarqube-scanner@4.2.6: + dependencies: + adm-zip: 0.5.12 + axios: 1.7.7 + commander: 12.0.0 + fs-extra: 11.2.0 + hpagent: 1.2.0 + jest-sonar-reporter: 2.0.0 + node-forge: 1.3.1 + properties-file: 3.5.4 + proxy-from-env: 1.1.0 + semver: 7.6.0 + slugify: 1.6.6 + tar-stream: 3.1.7 + transitivePeerDependencies: + - debug + source-map-js@1.2.1: {} source-map-support@0.5.13: @@ -13826,6 +13954,14 @@ snapshots: streamsearch@1.1.0: {} + streamx@2.21.1: + dependencies: + fast-fifo: 1.3.2 + queue-tick: 1.0.1 + text-decoder: 1.2.3 + optionalDependencies: + bare-events: 2.5.4 + string-argv@0.3.2: {} string-length@4.0.2: @@ -14025,6 +14161,12 @@ snapshots: tapable@2.2.1: {} + tar-stream@3.1.7: + dependencies: + b4a: 1.6.7 + fast-fifo: 1.3.2 + streamx: 2.21.1 + tar@7.4.3: dependencies: '@isaacs/fs-minipass': 4.0.1 @@ -14063,6 +14205,10 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 + text-decoder@1.2.3: + dependencies: + b4a: 1.6.7 + text-hex@1.0.0: {} text-table@0.2.0: {} @@ -14604,6 +14750,8 @@ snapshots: bufferutil: 4.0.8 utf-8-validate: 5.0.10 + xml@1.0.1: {} + xmlhttprequest-ssl@2.1.2: {} xtend@4.0.2: {} From d33ae85bda6a5cefff2050e6f3dc604e6783967a Mon Sep 17 00:00:00 2001 From: zero0205 Date: Wed, 22 Jan 2025 03:12:49 +0900 Subject: [PATCH 2/8] =?UTF-8?q?:recycle:=20[Refactor]:=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20props=EB=A5=BC=20read-only?= =?UTF-8?q?=EB=A1=9C=20=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/app/providers/AuthProvider.tsx | 3 ++- apps/client/src/app/providers/Providers.tsx | 3 ++- apps/client/src/app/providers/ThemeProvider.tsx | 3 ++- apps/client/src/app/providers/types.ts | 1 + .../broadcasting/ui/BroadcastPlayer/BroadcastPlayer.tsx | 4 ++-- .../client/src/features/broadcasting/ui/BroadcastTitle.tsx | 4 ++-- apps/client/src/features/broadcasting/ui/RecordButton.tsx | 4 ++-- apps/client/src/features/chatting/ui/ChatContainer.tsx | 7 ++++++- apps/client/src/features/chatting/ui/ChatEndModal.tsx | 4 ++-- apps/client/src/features/editProfile/ui/EditUserInfo.tsx | 4 ++-- .../features/watching/ui/LiveCamperInfo/LiveCamperInfo.tsx | 2 +- apps/client/src/features/watching/ui/LivePlayer/types.ts | 4 ++-- apps/client/src/pages/Profile/ui/UserInfo.tsx | 4 ++-- apps/client/src/pages/Record/ui/RecordInfo.tsx | 2 +- apps/client/src/pages/Record/ui/RecordList.tsx | 4 ++-- apps/client/src/pages/Record/ui/RecordPlayer.tsx | 2 +- apps/client/src/shared/ui/IconButton.tsx | 4 ++-- apps/client/src/shared/ui/Icons/BlogIcon.tsx | 2 +- apps/client/src/shared/ui/Icons/CloseIcon.tsx | 2 +- apps/client/src/shared/ui/Icons/EditIcon.tsx | 2 +- apps/client/src/shared/ui/Icons/GithubIcon.tsx | 2 +- apps/client/src/shared/ui/Icons/GoogleIcon.tsx | 2 +- apps/client/src/shared/ui/Icons/LinkedInIcon.tsx | 2 +- apps/client/src/shared/ui/Icons/Logo.tsx | 4 ++-- apps/client/src/shared/ui/Icons/MailIcon.tsx | 2 +- apps/client/src/shared/ui/Icons/ScreenShareIcon.tsx | 2 +- apps/client/src/shared/ui/Icons/ScreenShareOffIcon.tsx | 2 +- apps/client/src/shared/ui/Icons/SearchIcon.tsx | 2 +- apps/client/src/shared/ui/Icons/ThemeIcon.tsx | 2 +- apps/client/src/shared/ui/Icons/index.ts | 5 ----- apps/client/src/shared/ui/Icons/types.ts | 4 ++++ apps/client/src/shared/ui/Modal.tsx | 4 ++-- apps/client/src/shared/ui/character/DefaultCharacter.tsx | 4 ++-- apps/client/src/shared/ui/character/ErrorCharacter.tsx | 7 +++++-- apps/client/src/shared/ui/character/LoadingCharacter.tsx | 6 +++++- apps/client/src/shared/ui/character/types.ts | 4 ---- apps/client/src/widgets/Header/WelcomeCharacter.tsx | 4 ++-- apps/client/src/widgets/LiveList/FieldFilter.tsx | 4 ++-- apps/client/src/widgets/LiveList/LiveCard.tsx | 4 ++-- apps/client/src/widgets/LiveList/Search.tsx | 4 ++-- 40 files changed, 73 insertions(+), 62 deletions(-) create mode 100644 apps/client/src/app/providers/types.ts create mode 100644 apps/client/src/shared/ui/Icons/types.ts delete mode 100644 apps/client/src/shared/ui/character/types.ts diff --git a/apps/client/src/app/providers/AuthProvider.tsx b/apps/client/src/app/providers/AuthProvider.tsx index c1cdb67a..2829e9f2 100644 --- a/apps/client/src/app/providers/AuthProvider.tsx +++ b/apps/client/src/app/providers/AuthProvider.tsx @@ -1,7 +1,8 @@ import { useMemo, useState } from 'react'; import { AuthContext } from '@/shared/contexts'; +import { ProviderProps } from './types'; -export function AuthProvider({ children }: { children: React.ReactNode }) { +export function AuthProvider({ children }: ProviderProps) { const [isLoggedIn, setIsLoggedIn] = useState(() => !!localStorage.getItem('accessToken')); const value = useMemo(() => ({ isLoggedIn, setIsLoggedIn }), [isLoggedIn, setIsLoggedIn]); return {children}; diff --git a/apps/client/src/app/providers/Providers.tsx b/apps/client/src/app/providers/Providers.tsx index 9edec631..09c24660 100644 --- a/apps/client/src/app/providers/Providers.tsx +++ b/apps/client/src/app/providers/Providers.tsx @@ -1,7 +1,8 @@ import { ThemeProvider } from '@/app/providers/ThemeProvider'; import { AuthProvider } from '@/app/providers/AuthProvider'; +import { ProviderProps } from './types'; -export function Providers({ children }: { children: React.ReactNode }) { +export function Providers({ children }: ProviderProps) { return ( {children} diff --git a/apps/client/src/app/providers/ThemeProvider.tsx b/apps/client/src/app/providers/ThemeProvider.tsx index 9ad225c1..ad5f6be8 100644 --- a/apps/client/src/app/providers/ThemeProvider.tsx +++ b/apps/client/src/app/providers/ThemeProvider.tsx @@ -1,9 +1,10 @@ import { useMemo, useState } from 'react'; import { ThemeContext } from '@/shared/contexts'; +import { ProviderProps } from './types'; type Theme = 'light' | 'dark' | null; -export function ThemeProvider({ children }: { children: React.ReactNode }) { +export function ThemeProvider({ children }: ProviderProps) { const [theme, setTheme] = useState(() => (localStorage.getItem('theme') as Theme) ?? null); const value = useMemo(() => ({ theme, setTheme }), [theme, setTheme]); return {children}; diff --git a/apps/client/src/app/providers/types.ts b/apps/client/src/app/providers/types.ts new file mode 100644 index 00000000..0820549d --- /dev/null +++ b/apps/client/src/app/providers/types.ts @@ -0,0 +1 @@ +export type ProviderProps = Readonly<{ children: React.ReactNode }>; diff --git a/apps/client/src/features/broadcasting/ui/BroadcastPlayer/BroadcastPlayer.tsx b/apps/client/src/features/broadcasting/ui/BroadcastPlayer/BroadcastPlayer.tsx index b18219cb..6039076d 100644 --- a/apps/client/src/features/broadcasting/ui/BroadcastPlayer/BroadcastPlayer.tsx +++ b/apps/client/src/features/broadcasting/ui/BroadcastPlayer/BroadcastPlayer.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef } from 'react'; import { RESOLUTION_OPTIONS } from './resolutionOptions'; import { Tracks } from '../../../../features/broadcasting/model/trackTypes'; -type BroadcastPlayerProps = { +type BroadcastPlayerProps = Readonly<{ mediaStream: MediaStream | null; screenStream: MediaStream | null; isVideoEnabled: boolean; @@ -10,7 +10,7 @@ type BroadcastPlayerProps = { isStreamReady: boolean; setIsStreamReady: (ready: boolean) => void; tracksRef: React.MutableRefObject; -}; +}>; export function BroadcastPlayer({ mediaStream, diff --git a/apps/client/src/features/broadcasting/ui/BroadcastTitle.tsx b/apps/client/src/features/broadcasting/ui/BroadcastTitle.tsx index a6f2e668..581cecb3 100644 --- a/apps/client/src/features/broadcasting/ui/BroadcastTitle.tsx +++ b/apps/client/src/features/broadcasting/ui/BroadcastTitle.tsx @@ -7,10 +7,10 @@ type Inputs = { title: string; }; -type BroadcastTitleProps = { +type BroadcastTitleProps = Readonly<{ currentTitle: string; onTitleChange: (newTitle: string) => void; -}; +}>; export function BroadcastTitle({ currentTitle, onTitleChange }: BroadcastTitleProps) { const { diff --git a/apps/client/src/features/broadcasting/ui/RecordButton.tsx b/apps/client/src/features/broadcasting/ui/RecordButton.tsx index 7cdf034d..baf541a4 100644 --- a/apps/client/src/features/broadcasting/ui/RecordButton.tsx +++ b/apps/client/src/features/broadcasting/ui/RecordButton.tsx @@ -9,10 +9,10 @@ type FormInput = { title: string; }; -type RecordButtonProps = { +type RecordButtonProps = Readonly<{ socket: Socket | null; roomId: string; -}; +}>; export function RecordButton({ socket, roomId }: RecordButtonProps) { const [isRecording, setIsRecording] = useState(false); diff --git a/apps/client/src/features/chatting/ui/ChatContainer.tsx b/apps/client/src/features/chatting/ui/ChatContainer.tsx index ee062824..1578c906 100644 --- a/apps/client/src/features/chatting/ui/ChatContainer.tsx +++ b/apps/client/src/features/chatting/ui/ChatContainer.tsx @@ -11,7 +11,12 @@ import { Chat } from './types'; const chatServerUrl = import.meta.env.VITE_CHAT_SERVER_URL; -export function ChatContainer({ roomId, isProducer }: { roomId: string; isProducer: boolean }) { +type ChatContainerProps = Readonly<{ + roomId: string; + isProducer: boolean; +}>; + +export function ChatContainer({ roomId, isProducer }: ChatContainerProps) { const { isLoggedIn } = useContext(AuthContext); // 채팅 방 입장 const isJoinedRoomRef = useRef(false); diff --git a/apps/client/src/features/chatting/ui/ChatEndModal.tsx b/apps/client/src/features/chatting/ui/ChatEndModal.tsx index ec5f0cca..c17d8027 100644 --- a/apps/client/src/features/chatting/ui/ChatEndModal.tsx +++ b/apps/client/src/features/chatting/ui/ChatEndModal.tsx @@ -1,9 +1,9 @@ import { useNavigate } from 'react-router-dom'; import { Modal } from '@/shared/ui'; -type ChatEndModalProps = { +type ChatEndModalProps = Readonly<{ setShowModal: (b: boolean) => void; -}; +}>; export function ChatEndModal({ setShowModal }: ChatEndModalProps) { const navigate = useNavigate(); diff --git a/apps/client/src/features/editProfile/ui/EditUserInfo.tsx b/apps/client/src/features/editProfile/ui/EditUserInfo.tsx index 5bc06a4e..f8f61248 100644 --- a/apps/client/src/features/editProfile/ui/EditUserInfo.tsx +++ b/apps/client/src/features/editProfile/ui/EditUserInfo.tsx @@ -7,10 +7,10 @@ import { Button } from '@/shared/ui/shadcn/button'; import { axiosInstance } from '@/shared/api'; import { useToast } from '@/shared/lib'; -type EditUserInfoProps = { +type EditUserInfoProps = Readonly<{ userData: UserData | undefined; toggleEditing: () => void; -}; +}>; export type FormInput = { camperId: string | undefined; diff --git a/apps/client/src/features/watching/ui/LiveCamperInfo/LiveCamperInfo.tsx b/apps/client/src/features/watching/ui/LiveCamperInfo/LiveCamperInfo.tsx index 4beb9e98..6a164152 100644 --- a/apps/client/src/features/watching/ui/LiveCamperInfo/LiveCamperInfo.tsx +++ b/apps/client/src/features/watching/ui/LiveCamperInfo/LiveCamperInfo.tsx @@ -12,7 +12,7 @@ import { } from '@/shared/ui'; import { LiveInfo } from './types'; -export function LiveCamperInfo({ liveId }: { liveId: string }) { +export function LiveCamperInfo({ liveId }: Readonly<{ liveId: string }>) { const { data, isLoading, error } = useAPI({ url: `v1/broadcasts/${liveId}/info` }); if (error || !data) { diff --git a/apps/client/src/features/watching/ui/LivePlayer/types.ts b/apps/client/src/features/watching/ui/LivePlayer/types.ts index 6e0df374..8d9fd61b 100644 --- a/apps/client/src/features/watching/ui/LivePlayer/types.ts +++ b/apps/client/src/features/watching/ui/LivePlayer/types.ts @@ -6,11 +6,11 @@ export type Errors = { consumerError: Error | null; }; -export type LivePlayerProps = { +export type LivePlayerProps = Readonly<{ mediaStream: MediaStream | null; socket: Socket | null; transportId: string | undefined; errors: Errors; -}; +}>; export type VideoQuality = 'auto' | '480p' | '720p' | '1080p'; diff --git a/apps/client/src/pages/Profile/ui/UserInfo.tsx b/apps/client/src/pages/Profile/ui/UserInfo.tsx index d72e64a2..437d9802 100644 --- a/apps/client/src/pages/Profile/ui/UserInfo.tsx +++ b/apps/client/src/pages/Profile/ui/UserInfo.tsx @@ -3,12 +3,12 @@ import { ErrorCharacter, LoadingCharacter, BlogIcon, EditIcon, GithubIcon, Linke import { Avatar, AvatarFallback, AvatarImage } from '@/shared/ui/shadcn/avatar'; import { UserData } from '../model'; -type UserInfoProps = { +type UserInfoProps = Readonly<{ userData: UserData | undefined; isLoading: boolean; error: Error | null; toggleEditing: () => void; -}; +}>; export function UserInfo({ userData, isLoading, error, toggleEditing }: UserInfoProps) { const [showLoading, setShowLoading] = useState(false); diff --git a/apps/client/src/pages/Record/ui/RecordInfo.tsx b/apps/client/src/pages/Record/ui/RecordInfo.tsx index 3605c750..e8bca509 100644 --- a/apps/client/src/pages/Record/ui/RecordInfo.tsx +++ b/apps/client/src/pages/Record/ui/RecordInfo.tsx @@ -1,4 +1,4 @@ -export function RecordInfo({ title }: { title: string }) { +export function RecordInfo({ title }: Readonly<{ title: string }>) { return (

{title}

diff --git a/apps/client/src/pages/Record/ui/RecordList.tsx b/apps/client/src/pages/Record/ui/RecordList.tsx index 3b47f96d..9590d40d 100644 --- a/apps/client/src/pages/Record/ui/RecordList.tsx +++ b/apps/client/src/pages/Record/ui/RecordList.tsx @@ -4,9 +4,9 @@ import { PlayIcon, ErrorCharacter } from '@/shared/ui'; import { RecordData } from '../RecordPage'; import { axiosInstance } from '@/shared/api'; -type RecordListProps = { +type RecordListProps = Readonly<{ onClickList: (data: RecordData) => void; -}; +}>; export function RecordList({ onClickList }: RecordListProps) { const [recordList, setRecordList] = useState([]); diff --git a/apps/client/src/pages/Record/ui/RecordPlayer.tsx b/apps/client/src/pages/Record/ui/RecordPlayer.tsx index d36dfd00..6779a8fd 100644 --- a/apps/client/src/pages/Record/ui/RecordPlayer.tsx +++ b/apps/client/src/pages/Record/ui/RecordPlayer.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; import ReactPlayer from 'react-player'; import { LoadingCharacter } from '@/shared/ui'; -export function RecordPlayer({ video }: { video: string }) { +export function RecordPlayer({ video }: Readonly<{ video: string }>) { const [isSelectedVideo, setIsSelectedVideo] = useState(false); useEffect(() => { diff --git a/apps/client/src/shared/ui/IconButton.tsx b/apps/client/src/shared/ui/IconButton.tsx index 9c43165d..62fe9d42 100644 --- a/apps/client/src/shared/ui/IconButton.tsx +++ b/apps/client/src/shared/ui/IconButton.tsx @@ -1,11 +1,11 @@ -type IconButtonProps = { +type IconButtonProps = Readonly<{ children: React.ReactNode; title?: string; ariaLabel?: string; onClick?: () => void; disabled?: boolean; className?: string; -}; +}>; export function IconButton({ children, title, ariaLabel, onClick, disabled, className }: IconButtonProps) { return ( diff --git a/apps/client/src/shared/ui/Icons/BlogIcon.tsx b/apps/client/src/shared/ui/Icons/BlogIcon.tsx index 7286e70d..105ee548 100644 --- a/apps/client/src/shared/ui/Icons/BlogIcon.tsx +++ b/apps/client/src/shared/ui/Icons/BlogIcon.tsx @@ -1,4 +1,4 @@ -import { IconProps } from '.'; +import { IconProps } from './types'; function BlogIcon({ size = 24, className = '' }: IconProps) { return ( diff --git a/apps/client/src/shared/ui/Icons/CloseIcon.tsx b/apps/client/src/shared/ui/Icons/CloseIcon.tsx index e4f00cf9..bc6b0282 100644 --- a/apps/client/src/shared/ui/Icons/CloseIcon.tsx +++ b/apps/client/src/shared/ui/Icons/CloseIcon.tsx @@ -1,4 +1,4 @@ -import { IconProps } from '.'; +import { IconProps } from './types'; function CloseIcon({ size = 24, className }: IconProps) { return ( diff --git a/apps/client/src/shared/ui/Icons/EditIcon.tsx b/apps/client/src/shared/ui/Icons/EditIcon.tsx index 97b6901d..11b2f28b 100644 --- a/apps/client/src/shared/ui/Icons/EditIcon.tsx +++ b/apps/client/src/shared/ui/Icons/EditIcon.tsx @@ -1,4 +1,4 @@ -import { IconProps } from '.'; +import { IconProps } from './types'; function EditIcon({ size = 24, className }: IconProps) { return ( diff --git a/apps/client/src/shared/ui/Icons/GithubIcon.tsx b/apps/client/src/shared/ui/Icons/GithubIcon.tsx index e1ee191d..3d5064f6 100644 --- a/apps/client/src/shared/ui/Icons/GithubIcon.tsx +++ b/apps/client/src/shared/ui/Icons/GithubIcon.tsx @@ -1,4 +1,4 @@ -import { IconProps } from '.'; +import { IconProps } from './types'; function GithubIcon({ size = 20, className = '' }: IconProps) { return ( diff --git a/apps/client/src/shared/ui/Icons/GoogleIcon.tsx b/apps/client/src/shared/ui/Icons/GoogleIcon.tsx index 2e2c7996..002fd249 100644 --- a/apps/client/src/shared/ui/Icons/GoogleIcon.tsx +++ b/apps/client/src/shared/ui/Icons/GoogleIcon.tsx @@ -1,4 +1,4 @@ -import { IconProps } from '.'; +import { IconProps } from './types'; function GoogleIcon({ size = 60, className = '' }: IconProps) { return ( diff --git a/apps/client/src/shared/ui/Icons/LinkedInIcon.tsx b/apps/client/src/shared/ui/Icons/LinkedInIcon.tsx index 935fc9bc..afbe327c 100644 --- a/apps/client/src/shared/ui/Icons/LinkedInIcon.tsx +++ b/apps/client/src/shared/ui/Icons/LinkedInIcon.tsx @@ -1,4 +1,4 @@ -import { IconProps } from '.'; +import { IconProps } from './types'; function LinkedInIcon({ size = 20, className = '' }: IconProps) { return ( diff --git a/apps/client/src/shared/ui/Icons/Logo.tsx b/apps/client/src/shared/ui/Icons/Logo.tsx index 44e00e9d..8c90df0a 100644 --- a/apps/client/src/shared/ui/Icons/Logo.tsx +++ b/apps/client/src/shared/ui/Icons/Logo.tsx @@ -1,8 +1,8 @@ -interface LogoProps { +type LogoProps = Readonly<{ height?: number; width?: number; className?: string; -} +}>; function Logo({ height, width, className = '' }: LogoProps) { return ( diff --git a/apps/client/src/shared/ui/Icons/MailIcon.tsx b/apps/client/src/shared/ui/Icons/MailIcon.tsx index da7f4104..4ab376e6 100644 --- a/apps/client/src/shared/ui/Icons/MailIcon.tsx +++ b/apps/client/src/shared/ui/Icons/MailIcon.tsx @@ -1,4 +1,4 @@ -import { IconProps } from '.'; +import { IconProps } from './types'; function MailIcon({ size = 24, className = '' }: IconProps) { return ( diff --git a/apps/client/src/shared/ui/Icons/ScreenShareIcon.tsx b/apps/client/src/shared/ui/Icons/ScreenShareIcon.tsx index b0a41ca6..ad3da9bf 100644 --- a/apps/client/src/shared/ui/Icons/ScreenShareIcon.tsx +++ b/apps/client/src/shared/ui/Icons/ScreenShareIcon.tsx @@ -1,4 +1,4 @@ -import { IconProps } from '.'; +import { IconProps } from './types'; function ScreenShareIcon({ size = 24, className = '' }: IconProps) { return ( diff --git a/apps/client/src/shared/ui/Icons/ScreenShareOffIcon.tsx b/apps/client/src/shared/ui/Icons/ScreenShareOffIcon.tsx index 73629857..6fe97d70 100644 --- a/apps/client/src/shared/ui/Icons/ScreenShareOffIcon.tsx +++ b/apps/client/src/shared/ui/Icons/ScreenShareOffIcon.tsx @@ -1,4 +1,4 @@ -import { IconProps } from '.'; +import { IconProps } from './types'; function ScreenShareOffIcon({ size = 24, className = '' }: IconProps) { return ( diff --git a/apps/client/src/shared/ui/Icons/SearchIcon.tsx b/apps/client/src/shared/ui/Icons/SearchIcon.tsx index 487c0a2a..f9e750d2 100644 --- a/apps/client/src/shared/ui/Icons/SearchIcon.tsx +++ b/apps/client/src/shared/ui/Icons/SearchIcon.tsx @@ -1,4 +1,4 @@ -import { IconProps } from '.'; +import { IconProps } from './types'; function SearchIcon({ size = 24, className = '' }: IconProps) { return ( diff --git a/apps/client/src/shared/ui/Icons/ThemeIcon.tsx b/apps/client/src/shared/ui/Icons/ThemeIcon.tsx index d6d6926a..3dce18ee 100644 --- a/apps/client/src/shared/ui/Icons/ThemeIcon.tsx +++ b/apps/client/src/shared/ui/Icons/ThemeIcon.tsx @@ -1,4 +1,4 @@ -import { IconProps } from '.'; +import { IconProps } from './types'; function ThemeIcon({ size = 36, className = '' }: IconProps) { return ( diff --git a/apps/client/src/shared/ui/Icons/index.ts b/apps/client/src/shared/ui/Icons/index.ts index 18476f4f..125fd1d5 100644 --- a/apps/client/src/shared/ui/Icons/index.ts +++ b/apps/client/src/shared/ui/Icons/index.ts @@ -1,8 +1,3 @@ -export type IconProps = { - size?: number; - className?: string; -}; - export { default as BlogIcon } from './BlogIcon'; export { default as CloseIcon } from './CloseIcon'; export { default as EditIcon } from './EditIcon'; diff --git a/apps/client/src/shared/ui/Icons/types.ts b/apps/client/src/shared/ui/Icons/types.ts new file mode 100644 index 00000000..3e824c35 --- /dev/null +++ b/apps/client/src/shared/ui/Icons/types.ts @@ -0,0 +1,4 @@ +export type IconProps = Readonly<{ + size?: number; + className?: string; +}>; diff --git a/apps/client/src/shared/ui/Modal.tsx b/apps/client/src/shared/ui/Modal.tsx index e6189571..565b74d6 100644 --- a/apps/client/src/shared/ui/Modal.tsx +++ b/apps/client/src/shared/ui/Modal.tsx @@ -1,11 +1,11 @@ import { useEffect, useRef } from 'react'; import { CloseIcon } from '@/shared/ui/Icons'; -type ModalProps = { +type ModalProps = Readonly<{ children: React.ReactNode; setShowModal: (showModal: boolean) => void; modalClassName?: string; -}; +}>; export function Modal({ children, setShowModal, modalClassName }: ModalProps) { const modalRef = useRef(null); diff --git a/apps/client/src/shared/ui/character/DefaultCharacter.tsx b/apps/client/src/shared/ui/character/DefaultCharacter.tsx index 3ac22c56..d9ae1214 100644 --- a/apps/client/src/shared/ui/character/DefaultCharacter.tsx +++ b/apps/client/src/shared/ui/character/DefaultCharacter.tsx @@ -1,7 +1,7 @@ -type IconProps = { +type IconProps = Readonly<{ size?: number; className?: string; -}; +}>; export function DefaultCharacter({ size = 24, className = '' }: IconProps) { return ( diff --git a/apps/client/src/shared/ui/character/ErrorCharacter.tsx b/apps/client/src/shared/ui/character/ErrorCharacter.tsx index 059ffec0..2a9cdc0f 100644 --- a/apps/client/src/shared/ui/character/ErrorCharacter.tsx +++ b/apps/client/src/shared/ui/character/ErrorCharacter.tsx @@ -1,6 +1,9 @@ -import { ErrorCharacterProps } from './types'; +type ErrorCharacterProps = Readonly<{ + size?: number; + message?: string; +}>; -export function ErrorCharacter({ size = 300, message = 'Error' }: ErrorCharacterProps): JSX.Element { +export function ErrorCharacter({ size = 300, message = 'Error' }: ErrorCharacterProps) { return (
diff --git a/apps/client/src/shared/ui/character/LoadingCharacter.tsx b/apps/client/src/shared/ui/character/LoadingCharacter.tsx index b71817b4..9d25c2fd 100644 --- a/apps/client/src/shared/ui/character/LoadingCharacter.tsx +++ b/apps/client/src/shared/ui/character/LoadingCharacter.tsx @@ -1,4 +1,8 @@ -export function LoadingCharacter({ size = 300 }: { size?: number }): JSX.Element { +type LoadingCharacterProps = Readonly<{ + size?: number; +}>; + +export function LoadingCharacter({ size = 300 }: LoadingCharacterProps) { return (
diff --git a/apps/client/src/shared/ui/character/types.ts b/apps/client/src/shared/ui/character/types.ts deleted file mode 100644 index 01c8641b..00000000 --- a/apps/client/src/shared/ui/character/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type ErrorCharacterProps = { - size?: number; - message?: string; -}; diff --git a/apps/client/src/widgets/Header/WelcomeCharacter.tsx b/apps/client/src/widgets/Header/WelcomeCharacter.tsx index 1864950f..e8c83e6d 100644 --- a/apps/client/src/widgets/Header/WelcomeCharacter.tsx +++ b/apps/client/src/widgets/Header/WelcomeCharacter.tsx @@ -1,7 +1,7 @@ -type Props = { +type Props = Readonly<{ size?: number; className?: string; -}; +}>; export function WelcomeCharacter({ size, className }: Props) { return ( diff --git a/apps/client/src/widgets/LiveList/FieldFilter.tsx b/apps/client/src/widgets/LiveList/FieldFilter.tsx index 8805f377..c33ea3da 100644 --- a/apps/client/src/widgets/LiveList/FieldFilter.tsx +++ b/apps/client/src/widgets/LiveList/FieldFilter.tsx @@ -4,9 +4,9 @@ import { Field } from '@/shared/types/sharedTypes'; const fields: Field[] = ['WEB', 'AND', 'IOS']; -type FieldFilterProps = { +type FieldFilterProps = Readonly<{ onClickFilterButton: (field: Field) => void; -}; +}>; export function FieldFilter({ onClickFilterButton }: FieldFilterProps) { const [selected, setSelected] = useState(''); diff --git a/apps/client/src/widgets/LiveList/LiveCard.tsx b/apps/client/src/widgets/LiveList/LiveCard.tsx index dd9188e6..33544701 100644 --- a/apps/client/src/widgets/LiveList/LiveCard.tsx +++ b/apps/client/src/widgets/LiveList/LiveCard.tsx @@ -1,12 +1,12 @@ import { useNavigate } from 'react-router-dom'; -type LiveCardProps = { +type LiveCardProps = Readonly<{ liveId: string; title: string; userId: string; profileUrl?: string; thumbnailUrl: string; -}; +}>; export function LiveCard({ liveId, title, userId, profileUrl, thumbnailUrl }: LiveCardProps) { const navigate = useNavigate(); diff --git a/apps/client/src/widgets/LiveList/Search.tsx b/apps/client/src/widgets/LiveList/Search.tsx index 5bbee8d9..d3fa7552 100644 --- a/apps/client/src/widgets/LiveList/Search.tsx +++ b/apps/client/src/widgets/LiveList/Search.tsx @@ -1,9 +1,9 @@ import { useForm } from 'react-hook-form'; import { IconButton, SearchIcon } from '@/shared/ui'; -type SearchProps = { +type SearchProps = Readonly<{ onSearch: (keyword: string) => void; -}; +}>; type FormInput = { keyword: string; From 3d6679f037119a86c61c1330c2d00c51d6799963 Mon Sep 17 00:00:00 2001 From: zero0205 Date: Sun, 9 Feb 2025 17:11:09 +0900 Subject: [PATCH 3/8] =?UTF-8?q?:recycle:=20[Refactor]:=20lazy=20Loading=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HomePage, AuthPage, ProtectedRoute를 제외한 페이지에 lazy loading 적용 - lazy loading이 적용된 컴포넌트에 Suspense와 로딩 UI 추가 - lazy loading 지원을 위해 페이지 컴포넌트 export 방식 변경 --- apps/client/src/app/routes/router.tsx | 40 ++++++++++++++++------- apps/client/src/pages/Broadcast/index.tsx | 2 +- apps/client/src/pages/Live/index.ts | 2 +- apps/client/src/pages/Profile/index.ts | 2 +- apps/client/src/pages/Record/index.ts | 2 +- 5 files changed, 33 insertions(+), 15 deletions(-) diff --git a/apps/client/src/app/routes/router.tsx b/apps/client/src/app/routes/router.tsx index 1da40b29..bdba7ffc 100644 --- a/apps/client/src/app/routes/router.tsx +++ b/apps/client/src/app/routes/router.tsx @@ -1,13 +1,16 @@ import { createBrowserRouter } from 'react-router-dom'; -import { HomePage } from '@pages/Home'; -import { LivePage } from '@pages/Live'; -import { BroadcastPage } from '@pages/Broadcast'; -import { AuthPage } from '@pages/Auth'; -import { RecordPage } from '@pages/Record'; -import { ProfilePage } from '@/pages/Profile'; +import { HomePage } from '@/pages/Home'; +import { AuthPage } from '@/pages/Auth'; import { Layout } from '@/app/layouts'; import ProtectedRoute from './ProtectedRoute'; import { routerOptions } from './config'; +import { lazy, Suspense } from 'react'; +import { LoadingCharacter } from '@/shared/ui'; + +const LivePage = lazy(() => import('@/pages/Live')); +const BroadcastPage = lazy(() => import('@/pages/Broadcast')); +const ProfilePage = lazy(() => import('@/pages/Profile')); +const RecordPage = lazy(() => import('@/pages/Record')); export const router = createBrowserRouter( [ @@ -21,7 +24,11 @@ export const router = createBrowserRouter( }, { path: 'live/:liveId', - element: , + element: ( + }> + + + ), }, { path: 'auth', @@ -33,12 +40,19 @@ export const router = createBrowserRouter( children: [ { path: 'profile', - element: , + element: ( + }> + + + ), }, - { path: 'record/:attendanceId', - element: , + element: ( + }> + + + ), }, ], }, @@ -50,7 +64,11 @@ export const router = createBrowserRouter( children: [ { path: '', - element: , + element: ( + }> + + + ), }, ], }, diff --git a/apps/client/src/pages/Broadcast/index.tsx b/apps/client/src/pages/Broadcast/index.tsx index d99b7143..66d7e53b 100644 --- a/apps/client/src/pages/Broadcast/index.tsx +++ b/apps/client/src/pages/Broadcast/index.tsx @@ -1 +1 @@ -export { BroadcastPage } from './BroadcastPage'; +export { BroadcastPage as default } from './BroadcastPage'; diff --git a/apps/client/src/pages/Live/index.ts b/apps/client/src/pages/Live/index.ts index 31ed0446..cb88e418 100644 --- a/apps/client/src/pages/Live/index.ts +++ b/apps/client/src/pages/Live/index.ts @@ -1 +1 @@ -export { LivePage } from './LivePage'; +export { LivePage as default } from './LivePage'; diff --git a/apps/client/src/pages/Profile/index.ts b/apps/client/src/pages/Profile/index.ts index 1010180f..ce680163 100644 --- a/apps/client/src/pages/Profile/index.ts +++ b/apps/client/src/pages/Profile/index.ts @@ -1,2 +1,2 @@ -export { ProfilePage } from './ProfilePage'; +export { ProfilePage as default } from './ProfilePage'; export type { UserData } from './model'; diff --git a/apps/client/src/pages/Record/index.ts b/apps/client/src/pages/Record/index.ts index b1875c25..b0981d1b 100644 --- a/apps/client/src/pages/Record/index.ts +++ b/apps/client/src/pages/Record/index.ts @@ -1 +1 @@ -export { RecordPage } from './RecordPage'; +export { RecordPage as default } from './RecordPage'; From c2a568be57646571dad9623387a4b2e19b107338 Mon Sep 17 00:00:00 2001 From: zero0205 Date: Sun, 9 Feb 2025 17:18:22 +0900 Subject: [PATCH 4/8] =?UTF-8?q?:recycle:=20[Refactor]:=20=EB=B0=A9?= =?UTF-8?q?=EC=86=A1=20=EC=86=A1=EC=B6=9C=20=EC=97=B0=EA=B2=B0=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=ED=9B=85=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - useRoom, useTransport, useProducer 훅을 하나의 훅으로 통합 - 높은 의존성과 낮은 재사용성 문제 해결 - 디버깅 용이성 개선 - SonarQube 이슈 4건 해결 - 코드 중첩도(Nesting) 4건 --- .../client/src/features/broadcasting/index.ts | 2 +- .../src/features/broadcasting/model/index.ts | 2 +- .../broadcasting/model/mediasoup/index.ts | 3 +- .../model/mediasoup/produceHelpers.ts | 117 ++++++++++++++ .../model/mediasoup/useProduce.ts | 73 +++++++++ .../model/mediasoup/useProducer.ts | 151 ------------------ .../broadcasting/model/mediasoup/useRoom.ts | 30 ---- .../src/pages/Broadcast/BroadcastPage.tsx | 30 +--- 8 files changed, 201 insertions(+), 207 deletions(-) create mode 100644 apps/client/src/features/broadcasting/model/mediasoup/produceHelpers.ts create mode 100644 apps/client/src/features/broadcasting/model/mediasoup/useProduce.ts delete mode 100644 apps/client/src/features/broadcasting/model/mediasoup/useProducer.ts delete mode 100644 apps/client/src/features/broadcasting/model/mediasoup/useRoom.ts diff --git a/apps/client/src/features/broadcasting/index.ts b/apps/client/src/features/broadcasting/index.ts index 4c18f876..207ddd03 100644 --- a/apps/client/src/features/broadcasting/index.ts +++ b/apps/client/src/features/broadcasting/index.ts @@ -1,4 +1,4 @@ export { BroadcastPlayer, BroadcastTitle, RecordButton } from './ui'; -export { useRoom, useProducer, useMedia, useScreenShare } from './model'; +export { useProduce, useMedia, useScreenShare } from './model'; export type { Tracks } from './model'; diff --git a/apps/client/src/features/broadcasting/model/index.ts b/apps/client/src/features/broadcasting/model/index.ts index 1c7e393c..f1b0b892 100644 --- a/apps/client/src/features/broadcasting/model/index.ts +++ b/apps/client/src/features/broadcasting/model/index.ts @@ -1,4 +1,4 @@ export { useMedia } from './useMedia'; -export { useRoom, useProducer } from './mediasoup'; +export { useProduce } from './mediasoup'; export { useScreenShare } from './useScreenShare'; export type { Tracks } from './trackTypes'; diff --git a/apps/client/src/features/broadcasting/model/mediasoup/index.ts b/apps/client/src/features/broadcasting/model/mediasoup/index.ts index c74ebb69..ab31d452 100644 --- a/apps/client/src/features/broadcasting/model/mediasoup/index.ts +++ b/apps/client/src/features/broadcasting/model/mediasoup/index.ts @@ -1,2 +1 @@ -export { useRoom } from './useRoom'; -export { useProducer } from './useProducer'; +export { useProduce } from './useProduce'; diff --git a/apps/client/src/features/broadcasting/model/mediasoup/produceHelpers.ts b/apps/client/src/features/broadcasting/model/mediasoup/produceHelpers.ts new file mode 100644 index 00000000..56280666 --- /dev/null +++ b/apps/client/src/features/broadcasting/model/mediasoup/produceHelpers.ts @@ -0,0 +1,117 @@ +import { RtpCapabilities } from 'mediasoup-client/lib/RtpParameters'; +import { Socket } from 'socket.io-client'; +import * as mediasoupClient from 'mediasoup-client'; +import { Transport } from 'mediasoup-client/lib/types'; +import { ConnectTransportResponse, TransportInfo } from '@/shared/types/mediasoupTypes'; +import { ENCODING_OPTIONS } from './encodingOptions'; + +export const getRoomId = (socket: Socket): Promise => + new Promise(resolve => { + socket.emit('createRoom', (response: { roomId: string }) => { + resolve(response.roomId); + }); + }); + +export const getRtpCapabilities = async (socket: Socket, roomId: string): Promise => + new Promise((resolve, reject) => { + socket.emit('getRtpCapabilities', { roomId }, (response: { rtpCapabilities: RtpCapabilities }) => { + if (response.rtpCapabilities) { + resolve(response.rtpCapabilities); + } else { + reject(new Error('getRtpCapabilities Error: RTP Capabilities를 받아오지 못했습니다.')); + } + }); + }); + +export const createDevice = async (rtpCapabilities: RtpCapabilities) => { + const newDevice = new mediasoupClient.Device(); + await newDevice.load({ + routerRtpCapabilities: rtpCapabilities, + }); + return newDevice; +}; + +export const connectTransport = async (socket: Socket, device: mediasoupClient.Device, roomId: string) => { + const transportInfo: TransportInfo = await new Promise(resolve => { + socket.emit('createTransport', { roomId, isProducer: true }, (response: TransportInfo) => { + resolve(response); + }); + }); + + const transport = device.createSendTransport({ + id: transportInfo.transportId, + iceParameters: transportInfo.iceParameters, + iceCandidates: transportInfo.iceCandidates, + dtlsParameters: transportInfo.dtlsParameters, + }); + + transport.on('connect', async (parameters, callback) => { + socket.emit( + 'connectTransport', + { + roomId, + dtlsParameters: parameters.dtlsParameters, + transportId: transportInfo.transportId, + }, + (response: ConnectTransportResponse) => { + if (response.connected) { + callback(); + } + }, + ); + }); + + return { transport, transportInfo }; +}; + +export const createProducer = async ( + socket: Socket, + roomId: string, + transport: Transport, + transportInfo: TransportInfo, + mediaStream: MediaStream, +) => { + const handleProduce = (parameters: any, callback: any) => { + socket.emit( + 'createProducer', + { + roomId, + transportId: transportInfo.transportId, + kind: parameters.kind, + rtpParameters: parameters.rtpParameters, + }, + (response: { producerId: string }) => { + callback({ id: response.producerId }); + }, + ); + }; + + transport.on('produce', handleProduce); + + const producers = new Map(); + + try { + await Promise.all( + mediaStream.getTracks().map(async track => { + const producerConfig: Record = { + track, + stopTracks: false, + }; + + if (track.kind === 'video') { + producerConfig.encodings = ENCODING_OPTIONS; + producerConfig.codecOptions = { + videoGoogleStartBitrate: 1000, + }; + } + + const producer = await transport.produce(producerConfig); + producers.set(track.kind, producer); + }), + ); + + return producers; + } finally { + transport.off('produce', handleProduce); + } +}; diff --git a/apps/client/src/features/broadcasting/model/mediasoup/useProduce.ts b/apps/client/src/features/broadcasting/model/mediasoup/useProduce.ts new file mode 100644 index 00000000..2e1b9fea --- /dev/null +++ b/apps/client/src/features/broadcasting/model/mediasoup/useProduce.ts @@ -0,0 +1,73 @@ +import { Socket } from 'socket.io-client'; +import { useEffect, useRef, useState } from 'react'; +import { Producer, Transport } from 'mediasoup-client/lib/types'; +import { connectTransport, createDevice, createProducer, getRoomId, getRtpCapabilities } from './produceHelpers'; + +type UseProduceProps = { + socket: Socket | null; + mediaStream: MediaStream | null; +}; + +type UseProduceReturn = { + producers: Map; + error: Error | null; + roomId: string; + transport: Transport | null; +}; + +export const useProduce = ({ socket, mediaStream }: UseProduceProps): UseProduceReturn => { + const [error, setError] = useState(null); + const [roomId, setRoomId] = useState(''); + const producersRef = useRef>(new Map()); + const transportRef = useRef(null); + + useEffect(() => { + if (!socket || !mediaStream) return undefined; + const initializeProducer = async () => { + const newRoomId = await getRoomId(socket); + if (!newRoomId) { + setError(new Error('roomId가 없습니다.')); + return; + } + setRoomId(newRoomId); + + const rtpCapabilities = await getRtpCapabilities(socket, newRoomId); + if (!rtpCapabilities) { + setError(new Error('rtpCapabilities가 없습니다.')); + return; + } + + const device = await createDevice(rtpCapabilities); + if (!device) { + setError(new Error('device가 없습니다.')); + return; + } + + const { transport: newTransport, transportInfo } = await connectTransport(socket, device, newRoomId); + if (!newTransport || !transportInfo) { + setError(new Error('transport 연결에 문제가 발생했습니다.')); + return; + } + transportRef.current = newTransport; + + const newProducers = await createProducer(socket, newRoomId, newTransport, transportInfo, mediaStream); + producersRef.current = newProducers; + }; + + initializeProducer(); + + return () => { + producersRef.current.forEach(producer => producer.close()); + if (transportRef.current) { + transportRef.current.close(); + } + }; + }, [socket, mediaStream]); + + return { + roomId, + transport: transportRef.current, + producers: producersRef.current, + error, + }; +}; diff --git a/apps/client/src/features/broadcasting/model/mediasoup/useProducer.ts b/apps/client/src/features/broadcasting/model/mediasoup/useProducer.ts deleted file mode 100644 index 37a4e45d..00000000 --- a/apps/client/src/features/broadcasting/model/mediasoup/useProducer.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; -import { Transport, Device, Producer } from 'mediasoup-client/lib/types'; -import { Socket } from 'socket.io-client'; -import { TransportInfo } from '@/shared/types/mediasoupTypes'; -import { checkDependencies } from '@/shared/lib'; -import { ENCODING_OPTIONS } from './encodingOptions'; - -type UseProducerProps = { - socket: Socket | null; - // tracks: Tracks; - // isStreamReady: boolean; - mediaStream: MediaStream | null; - isMediaStreamReady: boolean; - roomId: string; - device: Device | null; - transportInfo: TransportInfo | null; -}; - -type UseProducerReturn = { - transport: Transport | null; - error: Error | null; - producerId: string; - producers: Map; -}; - -type ConnectTransportResponse = { - connected: boolean; - isProducer: boolean; -}; - -export const useProducer = ({ - socket, - mediaStream, - isMediaStreamReady, - roomId, - device, - transportInfo, -}: UseProducerProps): UseProducerReturn => { - const transportRef = useRef(null); - const [error, setError] = useState(null); - const [producerId, setProducerId] = useState(''); - const [producers, setProducers] = useState>(new Map()); - - useEffect(() => { - if (!socket || !device || !roomId || !mediaStream || !isMediaStreamReady || !transportInfo) { - return undefined; - } - - const createTransport = async () => { - if (!socket || !device || !roomId || !transportInfo) { - const dependencyError = checkDependencies('createTransport', { socket, device, roomId, transportInfo }); - setError(dependencyError); - return; - } - - setError(null); - - const newTransport = device.createSendTransport({ - id: transportInfo.transportId, - iceParameters: transportInfo.iceParameters, - iceCandidates: transportInfo.iceCandidates, - dtlsParameters: transportInfo.dtlsParameters, - }); - - transportRef.current = newTransport; - - transportRef.current.on('connect', async (parameters, callback) => { - socket.emit( - 'connectTransport', - { - roomId, - dtlsParameters: parameters.dtlsParameters, - transportId: transportInfo.transportId, - }, - (response: ConnectTransportResponse) => { - if (response.connected) { - callback(); - } - }, - ); - }); - }; - - const createProducer = async () => { - if (!transportRef.current || !transportInfo || !socket || !mediaStream) { - const dependencyError = checkDependencies('createProducer', { - socket, - mediaStream, - transport: transportRef.current, - transportInfo, - }); - setError(dependencyError); - return; - } - - setError(null); - - transportRef.current!.on('produce', (parameters, callback) => { - socket.emit( - 'createProducer', - { - roomId, - transportId: transportInfo.transportId, - kind: parameters.kind, - rtpParameters: parameters.rtpParameters, - }, - (response: { producerId: string }) => { - callback({ id: response.producerId }); - setProducerId(response.producerId); - }, - ); - }); - - mediaStream.getTracks().forEach(track => { - const producerConfig: Record = { - track, - stopTracks: false, - }; - - if (track.kind === 'video') { - producerConfig.encodings = ENCODING_OPTIONS; - producerConfig.codecOptions = { - videoGoogleStartBitrate: 1000, - }; - } - - transportRef.current!.produce(producerConfig).then(producer => { - setProducers(prev => new Map(prev).set(track.kind, producer)); - }); - }); - }; - - createTransport() - .then(() => createProducer()) - .catch(err => setError(err instanceof Error ? err : new Error('Producer initialization failed'))); - - return () => { - if (transportRef.current) { - transportRef.current.close(); - transportRef.current = null; - } - }; - }, [socket, device, roomId, transportInfo, isMediaStreamReady, mediaStream]); - - return { - transport: transportRef.current, - error, - producerId, - producers, - }; -}; diff --git a/apps/client/src/features/broadcasting/model/mediasoup/useRoom.ts b/apps/client/src/features/broadcasting/model/mediasoup/useRoom.ts deleted file mode 100644 index 524093af..00000000 --- a/apps/client/src/features/broadcasting/model/mediasoup/useRoom.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useEffect, useState } from 'react'; -import { Socket } from 'socket.io-client'; - -export const useRoom = (socket: Socket | null, isConnected: boolean, isMediaStreamReady: boolean) => { - const [roomId, setRoomId] = useState(''); - const [roomError, setRoomError] = useState(null); - - useEffect(() => { - if (!isMediaStreamReady) return; - - const getRooomId = async () => { - if (!socket) { - setRoomError(new Error('getRoomId Error: socket이 존재하지 않습니다.')); - return; - } - - setRoomError(null); - socket.emit('createRoom', (response: { roomId: string }) => { - setRoomId(response.roomId); - }); - }; - - getRooomId(); - }, [isConnected, isMediaStreamReady, socket]); - - return { - roomId, - roomError, - }; -}; diff --git a/apps/client/src/pages/Broadcast/BroadcastPage.tsx b/apps/client/src/pages/Broadcast/BroadcastPage.tsx index 35275a12..78eae4e0 100644 --- a/apps/client/src/pages/Broadcast/BroadcastPage.tsx +++ b/apps/client/src/pages/Broadcast/BroadcastPage.tsx @@ -9,8 +9,6 @@ import { ScreenShareOffIcon, } from '@/shared/ui'; import { - useRoom, - useProducer, useMedia, useScreenShare, Tracks, @@ -19,9 +17,10 @@ import { RecordButton, } from '@/features/broadcasting'; import { ChatContainer } from '@/features/chatting'; -import { useSocket, useTransport, useTheme } from '@/shared/lib'; +import { useSocket, useTheme } from '@/shared/lib'; import { Button } from '@/shared/ui/shadcn/button'; import { axiosInstance } from '@/shared/api'; +import { useProduce } from '@/features/broadcasting/model'; const mediaServerUrl = import.meta.env.VITE_MEDIASERVER_URL; @@ -30,7 +29,7 @@ export function BroadcastPage() { const { mediaStream, mediaStreamError, - isMediaStreamReady, + isMediaStreamReady: _, isAudioEnabled, isVideoEnabled, toggleAudio, @@ -43,21 +42,8 @@ export function BroadcastPage() { const tracksRef = useRef({ video: undefined, mediaAudio: undefined, screenAudio: undefined }); const [isStreamReady, setIsStreamReady] = useState(false); // 방송 송출 - const { socket, isConnected, socketError } = useSocket(mediaServerUrl); - const { roomId, roomError } = useRoom(socket, isConnected, isMediaStreamReady); - const { transportInfo, device, transportError } = useTransport({ socket, roomId, isProducer: true }); - const { - transport, - error: mediasoupError, - producers, - } = useProducer({ - socket, - mediaStream, - isMediaStreamReady, - transportInfo, - device, - roomId, - }); + const { socket, socketError } = useSocket(mediaServerUrl); + const { roomId, transport, producers, error: producerError } = useProduce({ socket, mediaStream }); // 방송 정보 const [title, setTitle] = useState(''); // 테마 @@ -153,7 +139,7 @@ export function BroadcastPage() { changeTrack(); }, [isVideoEnabled, isScreenSharing, mediaStream, screenStream, producers]); - if (socketError || roomError || transportError || screenShareError) { + if (socketError || screenShareError) { mediaStream?.getTracks().forEach((track: MediaStreamTrack) => track.stop()); return (
@@ -164,11 +150,11 @@ export function BroadcastPage() { return (
- {mediaStreamError || mediasoupError ? ( + {mediaStreamError || producerError ? ( <>

Error

{mediaStreamError &&
{mediaStreamError.message}
} - {mediasoupError &&
{mediasoupError.message}
} + {producerError &&
{producerError.message}
} ) : ( <> From fac49cf66696df67677afa5d26b5faf7275a1f7d Mon Sep 17 00:00:00 2001 From: zero0205 Date: Mon, 10 Feb 2025 01:16:51 +0900 Subject: [PATCH 5/8] =?UTF-8?q?:bug:=20[Fix]:=20useAPI=20=ED=9B=85?= =?UTF-8?q?=EC=9D=98=20=EB=AC=B4=ED=95=9C=20=EB=A6=AC=EB=A0=8C=EB=8D=94?= =?UTF-8?q?=EB=A7=81=20=EC=9D=B4=EC=8A=88=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 불필요한 리렌더링 및 API 요청을 하지 않도록 useAPI 훅의 API 요청 로직 구조 개선 - API 요청에 필요한 핵심 파라미터만 받도록 수정(endpoint, options) --- .../ui/LiveCamperInfo/LiveCamperInfo.tsx | 2 +- apps/client/src/shared/api/useAPI.ts | 47 ++++++++++++------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/apps/client/src/features/watching/ui/LiveCamperInfo/LiveCamperInfo.tsx b/apps/client/src/features/watching/ui/LiveCamperInfo/LiveCamperInfo.tsx index 6a164152..8242e001 100644 --- a/apps/client/src/features/watching/ui/LiveCamperInfo/LiveCamperInfo.tsx +++ b/apps/client/src/features/watching/ui/LiveCamperInfo/LiveCamperInfo.tsx @@ -13,7 +13,7 @@ import { import { LiveInfo } from './types'; export function LiveCamperInfo({ liveId }: Readonly<{ liveId: string }>) { - const { data, isLoading, error } = useAPI({ url: `v1/broadcasts/${liveId}/info` }); + const { data, isLoading, error } = useAPI(`v1/broadcasts/${liveId}/info`); if (error || !data) { return ( diff --git a/apps/client/src/shared/api/useAPI.ts b/apps/client/src/shared/api/useAPI.ts index 09a31f2f..32fe424d 100644 --- a/apps/client/src/shared/api/useAPI.ts +++ b/apps/client/src/shared/api/useAPI.ts @@ -1,32 +1,45 @@ -import { AxiosRequestConfig } from 'axios'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { axiosInstance } from '@/shared/api'; +type APIOptions = { + method?: 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH'; + params?: Record; + data?: unknown; +}; + type APIQueryState = { data: T | null; + fetchData: () => Promise; isLoading: boolean; error: Error | null; }; -export const useAPI = (apiInfo: AxiosRequestConfig): APIQueryState => { +export const useAPI = (endpoint: string, options: APIOptions = {}): APIQueryState => { const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); - const [data, setData] = useState(null); + const [data, setData] = useState(null); - useEffect(() => { - const fetchData = async () => { - try { - const result = await axiosInstance.request(apiInfo); - setData(result.data.data); - } catch (err) { - setError(err instanceof Error ? err : new Error()); - } finally { - setIsLoading(false); - } - }; + const fetchData = useCallback(async () => { + setIsLoading(true); + try { + const result = await axiosInstance.request({ + url: endpoint, + method: options.method ?? 'GET', + params: options.params, + data: options.data, + }); + setData(result.data.data); + setError(null); + } catch (err) { + setError(err instanceof Error ? err : new Error('Failed to fetch data.')); + } finally { + setIsLoading(false); + } + }, [endpoint, options.method, options.params, options.data]); + useEffect(() => { fetchData(); - }, [apiInfo]); + }, [fetchData]); - return { data, isLoading, error }; + return { data, fetchData, isLoading, error }; }; From 6b7da44d5617759109bfd929446cb2ab0fd3c470 Mon Sep 17 00:00:00 2001 From: zero0205 Date: Mon, 10 Feb 2025 01:47:15 +0900 Subject: [PATCH 6/8] =?UTF-8?q?:recycle:=20[Refactor]:=20=EB=B0=A9?= =?UTF-8?q?=EC=86=A1=20=EC=8B=9C=EC=B2=AD=20=EC=97=B0=EA=B2=B0=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=ED=9B=85=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - useTransport, useConsumer 훅을 하나의 훅으로 통합 - useProduce와 useConsume에서 공통적으로 사용되는 함수는 shared/lib로 이동 - 컴포넌트 간 의존성 개선 - 디버깅 로깅 추가 - SonarQube 중첩도 이슈 해결 --- apps/client/.eslintrc | 1 + apps/client/src/app/routes/router.tsx | 2 +- .../model/mediasoup/produceHelpers.ts | 56 +------ .../model/mediasoup/useProduce.ts | 5 +- apps/client/src/features/watching/index.ts | 3 +- .../features/watching/model/consumeHelpers.ts | 51 +++++++ .../src/features/watching/model/index.ts | 2 +- .../src/features/watching/model/useConsume.ts | 66 ++++++++ .../features/watching/model/useConsumer.ts | 142 ------------------ .../watching/ui/LivePlayer/LivePlayer.tsx | 6 +- .../features/watching/ui/LivePlayer/types.ts | 1 - apps/client/src/pages/Live/LivePage.tsx | 29 ++-- apps/client/src/shared/lib/index.ts | 2 +- .../client/src/shared/lib/mediasoupHelpers.ts | 73 +++++++++ 14 files changed, 213 insertions(+), 226 deletions(-) create mode 100644 apps/client/src/features/watching/model/consumeHelpers.ts create mode 100644 apps/client/src/features/watching/model/useConsume.ts delete mode 100644 apps/client/src/features/watching/model/useConsumer.ts create mode 100644 apps/client/src/shared/lib/mediasoupHelpers.ts diff --git a/apps/client/.eslintrc b/apps/client/.eslintrc index d1d35433..684ff1ea 100644 --- a/apps/client/.eslintrc +++ b/apps/client/.eslintrc @@ -54,6 +54,7 @@ "import/no-unresolved": "off", "import/extensions": ["off"], "import/prefer-default-export": "off", + "no-restricted-exports": "warn", // 접근성 관련 규칙 "jsx-a11y/media-has-caption": "off", diff --git a/apps/client/src/app/routes/router.tsx b/apps/client/src/app/routes/router.tsx index bdba7ffc..7049f81c 100644 --- a/apps/client/src/app/routes/router.tsx +++ b/apps/client/src/app/routes/router.tsx @@ -1,10 +1,10 @@ import { createBrowserRouter } from 'react-router-dom'; +import { lazy, Suspense } from 'react'; import { HomePage } from '@/pages/Home'; import { AuthPage } from '@/pages/Auth'; import { Layout } from '@/app/layouts'; import ProtectedRoute from './ProtectedRoute'; import { routerOptions } from './config'; -import { lazy, Suspense } from 'react'; import { LoadingCharacter } from '@/shared/ui'; const LivePage = lazy(() => import('@/pages/Live')); diff --git a/apps/client/src/features/broadcasting/model/mediasoup/produceHelpers.ts b/apps/client/src/features/broadcasting/model/mediasoup/produceHelpers.ts index 56280666..ed7d2c48 100644 --- a/apps/client/src/features/broadcasting/model/mediasoup/produceHelpers.ts +++ b/apps/client/src/features/broadcasting/model/mediasoup/produceHelpers.ts @@ -1,8 +1,6 @@ -import { RtpCapabilities } from 'mediasoup-client/lib/RtpParameters'; import { Socket } from 'socket.io-client'; -import * as mediasoupClient from 'mediasoup-client'; import { Transport } from 'mediasoup-client/lib/types'; -import { ConnectTransportResponse, TransportInfo } from '@/shared/types/mediasoupTypes'; +import { TransportInfo } from '@/shared/types/mediasoupTypes'; import { ENCODING_OPTIONS } from './encodingOptions'; export const getRoomId = (socket: Socket): Promise => @@ -12,58 +10,6 @@ export const getRoomId = (socket: Socket): Promise => }); }); -export const getRtpCapabilities = async (socket: Socket, roomId: string): Promise => - new Promise((resolve, reject) => { - socket.emit('getRtpCapabilities', { roomId }, (response: { rtpCapabilities: RtpCapabilities }) => { - if (response.rtpCapabilities) { - resolve(response.rtpCapabilities); - } else { - reject(new Error('getRtpCapabilities Error: RTP Capabilities를 받아오지 못했습니다.')); - } - }); - }); - -export const createDevice = async (rtpCapabilities: RtpCapabilities) => { - const newDevice = new mediasoupClient.Device(); - await newDevice.load({ - routerRtpCapabilities: rtpCapabilities, - }); - return newDevice; -}; - -export const connectTransport = async (socket: Socket, device: mediasoupClient.Device, roomId: string) => { - const transportInfo: TransportInfo = await new Promise(resolve => { - socket.emit('createTransport', { roomId, isProducer: true }, (response: TransportInfo) => { - resolve(response); - }); - }); - - const transport = device.createSendTransport({ - id: transportInfo.transportId, - iceParameters: transportInfo.iceParameters, - iceCandidates: transportInfo.iceCandidates, - dtlsParameters: transportInfo.dtlsParameters, - }); - - transport.on('connect', async (parameters, callback) => { - socket.emit( - 'connectTransport', - { - roomId, - dtlsParameters: parameters.dtlsParameters, - transportId: transportInfo.transportId, - }, - (response: ConnectTransportResponse) => { - if (response.connected) { - callback(); - } - }, - ); - }); - - return { transport, transportInfo }; -}; - export const createProducer = async ( socket: Socket, roomId: string, diff --git a/apps/client/src/features/broadcasting/model/mediasoup/useProduce.ts b/apps/client/src/features/broadcasting/model/mediasoup/useProduce.ts index 2e1b9fea..b1ba6266 100644 --- a/apps/client/src/features/broadcasting/model/mediasoup/useProduce.ts +++ b/apps/client/src/features/broadcasting/model/mediasoup/useProduce.ts @@ -1,7 +1,8 @@ import { Socket } from 'socket.io-client'; import { useEffect, useRef, useState } from 'react'; import { Producer, Transport } from 'mediasoup-client/lib/types'; -import { connectTransport, createDevice, createProducer, getRoomId, getRtpCapabilities } from './produceHelpers'; +import { createProducer, getRoomId } from './produceHelpers'; +import { connectTransport, createDevice, getRtpCapabilities } from '@/shared/lib'; type UseProduceProps = { socket: Socket | null; @@ -43,7 +44,7 @@ export const useProduce = ({ socket, mediaStream }: UseProduceProps): UseProduce return; } - const { transport: newTransport, transportInfo } = await connectTransport(socket, device, newRoomId); + const { transport: newTransport, transportInfo } = await connectTransport(socket, device, newRoomId, true); if (!newTransport || !transportInfo) { setError(new Error('transport 연결에 문제가 발생했습니다.')); return; diff --git a/apps/client/src/features/watching/index.ts b/apps/client/src/features/watching/index.ts index cfdae0ff..3ba42577 100644 --- a/apps/client/src/features/watching/index.ts +++ b/apps/client/src/features/watching/index.ts @@ -1,3 +1,2 @@ -export { useConsumer } from './model'; - export { LiveCamperInfo, LivePlayer } from './ui'; +export { useConsume } from './model'; diff --git a/apps/client/src/features/watching/model/consumeHelpers.ts b/apps/client/src/features/watching/model/consumeHelpers.ts new file mode 100644 index 00000000..60758b71 --- /dev/null +++ b/apps/client/src/features/watching/model/consumeHelpers.ts @@ -0,0 +1,51 @@ +import { MediaKind } from 'mediasoup-client/lib/RtpParameters'; +import { Socket } from 'socket.io-client'; +import { Transport } from 'mediasoup-client/lib/types'; +import { TransportInfo } from '@/shared/types/mediasoupTypes'; + +type CreateConsumer = { + consumerId: string; + producerId: string; + kind: MediaKind; + rtpParameters: any; +}; + +type CreateConsumerResponse = { + consumers: CreateConsumer[]; +}; + +export const createConsumer = async ( + socket: Socket, + roomId: string, + transport: Transport, + transportInfo: TransportInfo, +) => { + const mediaStream = new MediaStream(); + socket.emit( + 'createConsumer', + { + roomId, + transportId: transportInfo.transportId, + }, + async ({ consumers }: CreateConsumerResponse) => { + await Promise.all( + consumers.map(async consumerData => { + const consumer = await transport.consume({ + id: consumerData.consumerId, + producerId: consumerData.producerId, + rtpParameters: consumerData.rtpParameters, + kind: consumerData.kind, + }); + + if (consumer.track.kind === 'video') { + consumer.track.enabled = true; + } + mediaStream.addTrack(consumer.track); + consumer.resume(); + }), + ); + }, + ); + + return mediaStream; +}; diff --git a/apps/client/src/features/watching/model/index.ts b/apps/client/src/features/watching/model/index.ts index 851f3ff4..e5bfd955 100644 --- a/apps/client/src/features/watching/model/index.ts +++ b/apps/client/src/features/watching/model/index.ts @@ -1 +1 @@ -export { useConsumer } from './useConsumer'; +export { useConsume } from './useConsume'; diff --git a/apps/client/src/features/watching/model/useConsume.ts b/apps/client/src/features/watching/model/useConsume.ts new file mode 100644 index 00000000..0a0607db --- /dev/null +++ b/apps/client/src/features/watching/model/useConsume.ts @@ -0,0 +1,66 @@ +import { Transport } from 'mediasoup-client/lib/types'; +import { useEffect, useRef, useState } from 'react'; +import { Socket } from 'socket.io-client'; +import { connectTransport, createDevice, getRtpCapabilities } from '@/shared/lib'; +import { createConsumer } from './consumeHelpers'; + +type UseConsumerProps = { + socket: Socket | null; + roomId: string | undefined; +}; + +export const useConsume = ({ socket, roomId }: UseConsumerProps) => { + const transportRef = useRef(null); + const transportIdRef = useRef(undefined); + const mediastreamRef = useRef(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + if (!socket || !roomId) return undefined; + + const initializeConsumer = async () => { + try { + const rtpCapabilities = await getRtpCapabilities(socket, roomId); + if (!rtpCapabilities) { + setError(new Error('rtpCapabilities가 없습니다.')); + return; + } + + const device = await createDevice(rtpCapabilities); + if (!device) { + setError(new Error('device가 없습니다.')); + return; + } + + const { transport: newTransport, transportInfo } = await connectTransport(socket, device, roomId, false); + if (!newTransport || !transportInfo) { + setError(new Error('transport 연결에 문제가 발생했습니다.')); + return; + } + transportRef.current = newTransport; + transportIdRef.current = transportInfo.transportId; + + const newMediastream = await createConsumer(socket, roomId, newTransport, transportInfo); + mediastreamRef.current = newMediastream; + } finally { + setIsLoading(false); + } + }; + + initializeConsumer(); + + return () => { + transportRef.current?.close(); + mediastreamRef.current?.getTracks().map(track => track.stop()); + }; + }, [socket, roomId]); + + return { + transport: transportRef.current, + transportId: transportIdRef.current, + mediastream: mediastreamRef.current, + error, + isLoading, + }; +}; diff --git a/apps/client/src/features/watching/model/useConsumer.ts b/apps/client/src/features/watching/model/useConsumer.ts deleted file mode 100644 index 798fcf65..00000000 --- a/apps/client/src/features/watching/model/useConsumer.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; -import { Transport, Device, MediaKind } from 'mediasoup-client/lib/types'; -import { Socket } from 'socket.io-client'; -import { checkDependencies } from '@/shared/lib'; -import { TransportInfo } from '@/shared/types/mediasoupTypes'; - -type UseConsumerProps = { - socket: Socket | null; - device: Device | null; - roomId: string | undefined; - transportInfo: TransportInfo | null; - isConnected: boolean; -}; - -type CreateConsumer = { - consumerId: string; - producerId: string; - kind: MediaKind; - rtpParameters: any; -}; - -type CreateConsumerResponse = { - consumers: CreateConsumer[]; -}; - -type ConnectTransportResponse = { - connected: boolean; - isProducer: boolean; -}; - -export const useConsumer = ({ socket, device, roomId, transportInfo, isConnected }: UseConsumerProps) => { - const transportRef = useRef(null); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - const [mediastream, setMediastream] = useState(null); - - useEffect(() => { - if (!socket || !isConnected || !roomId || !transportInfo || !device) { - return undefined; - } - - const connectTransport = async () => { - if (!socket || !transportInfo || !device || !roomId) { - const dependencyError = checkDependencies('connectTransport', { socket, transportInfo, device, roomId }); - setError(dependencyError); - return; - } - - setError(null); - - const newTransport = device.createRecvTransport({ - id: transportInfo.transportId, - iceParameters: transportInfo.iceParameters, - iceCandidates: transportInfo.iceCandidates, - dtlsParameters: transportInfo.dtlsParameters, - }); - - transportRef.current = newTransport; - - transportRef.current.on('connect', async ({ dtlsParameters }, callback) => { - const connectTransportResponse = await new Promise((resolve, reject) => { - socket.emit( - 'connectTransport', - { - roomId, - dtlsParameters, - transportId: transportInfo.transportId, - }, - (response: ConnectTransportResponse) => { - if (response.connected) { - resolve(response); - } else { - reject(new Error('Transport connection failed')); - } - }, - ); - }); - callback(); - return connectTransportResponse; - }); - }; - - const createConsumer = async () => { - if (!transportRef.current || !socket || !transportInfo) { - const dependencyError = checkDependencies('createConsumer', { socket, transportRef, transportInfo }); - setError(dependencyError); - return; - } - - setError(null); - - socket.emit( - 'createConsumer', - { - roomId, - transportId: transportInfo.transportId, - }, - async ({ consumers }: CreateConsumerResponse) => { - const newMediastream = new MediaStream(); - await Promise.all( - consumers.map(async consumerData => { - const consumer = await transportRef.current!.consume({ - id: consumerData.consumerId, - producerId: consumerData.producerId, - rtpParameters: consumerData.rtpParameters, - kind: consumerData.kind, - }); - - if (consumer.track.kind === 'video') { - consumer.track.enabled = true; - } - newMediastream.addTrack(consumer.track); - consumer.resume(); - }), - ); - - setMediastream(newMediastream); - }, - ); - }; - - connectTransport() - .then(() => createConsumer()) - .then(() => setIsLoading(false)) - .catch(err => setError(err instanceof Error ? err : new Error('Consumer initialization failed'))); - - return () => { - if (transportRef.current) { - transportRef.current.close(); - transportRef.current = null; - } - setMediastream(null); - }; - }, [socket, isConnected, transportInfo, device, roomId]); - - return { - transport: transportRef.current, - mediastream, - error, - isLoading, - }; -}; diff --git a/apps/client/src/features/watching/ui/LivePlayer/LivePlayer.tsx b/apps/client/src/features/watching/ui/LivePlayer/LivePlayer.tsx index e1f5cdba..362e884a 100644 --- a/apps/client/src/features/watching/ui/LivePlayer/LivePlayer.tsx +++ b/apps/client/src/features/watching/ui/LivePlayer/LivePlayer.tsx @@ -10,7 +10,7 @@ export function LivePlayer({ mediaStream, socket, transportId, errors }: LivePla const [videoQuality, setVideoQuality] = useState('720p'); const videoRef = useRef(null); - const { socketError, transportError, consumerError } = errors; + const { socketError, consumerError } = errors; useEffect(() => { const videoElement = videoRef.current; @@ -19,7 +19,7 @@ export function LivePlayer({ mediaStream, socket, transportId, errors }: LivePla } return () => { - if (videoElement && videoElement.srcObject) { + if (videoElement?.srcObject) { videoElement.srcObject = null; } }; @@ -54,7 +54,7 @@ export function LivePlayer({ mediaStream, socket, transportId, errors }: LivePla await videoRef.current?.requestFullscreen?.(); }; - if (socketError || transportError || consumerError) { + if (socketError || consumerError) { return (
diff --git a/apps/client/src/features/watching/ui/LivePlayer/types.ts b/apps/client/src/features/watching/ui/LivePlayer/types.ts index 8d9fd61b..2dadb764 100644 --- a/apps/client/src/features/watching/ui/LivePlayer/types.ts +++ b/apps/client/src/features/watching/ui/LivePlayer/types.ts @@ -2,7 +2,6 @@ import { Socket } from 'socket.io-client'; export type Errors = { socketError: Error | null; - transportError: Error | null; consumerError: Error | null; }; diff --git a/apps/client/src/pages/Live/LivePage.tsx b/apps/client/src/pages/Live/LivePage.tsx index 7f8508ad..14cbcd68 100644 --- a/apps/client/src/pages/Live/LivePage.tsx +++ b/apps/client/src/pages/Live/LivePage.tsx @@ -2,37 +2,30 @@ import { useEffect } from 'react'; import { useParams } from 'react-router-dom'; import { ErrorCharacter } from '@/shared/ui'; import { ChatContainer } from '@/features/chatting'; -import { useConsumer, LivePlayer, LiveCamperInfo } from '@/features/watching'; -import { useSocket, useTransport } from '@/shared/lib'; +import { useConsume, LivePlayer, LiveCamperInfo } from '@/features/watching'; +import { useSocket } from '@/shared/lib'; const socketUrl = import.meta.env.VITE_MEDIASERVER_URL; export function LivePage() { const { liveId } = useParams<{ liveId: string }>(); - const { socket, isConnected, socketError } = useSocket(socketUrl); - const { transportInfo, device, transportError } = useTransport({ - socket, - roomId: liveId, - isProducer: false, - }); + const { socket, socketError } = useSocket(socketUrl); const { transport, + transportId, mediastream: mediaStream, error: consumerError, - } = useConsumer({ + } = useConsume({ socket, - device, roomId: liveId, - transportInfo, - isConnected, }); useEffect(() => { - if (!socket || !liveId || !transportInfo || !transport) return undefined; + if (!socket || !liveId || !transport) return undefined; const handleLeaveLive = () => { - if (socket && liveId && transportInfo) { - socket.emit('leaveBroadcast', { transportId: transportInfo.transportId, roomId: liveId }); + if (socket && liveId && transportId) { + socket.emit('leaveBroadcast', { transportId, roomId: liveId }); } socket?.disconnect(); @@ -51,7 +44,7 @@ export function LivePage() { handleLeaveLive(); window.removeEventListener('beforeunload', preventClose); }; - }, [socket, liveId, transportInfo, transport]); + }, [socket, liveId, transportId, transport]); return (
@@ -62,9 +55,9 @@ export function LivePage() {
diff --git a/apps/client/src/shared/lib/index.ts b/apps/client/src/shared/lib/index.ts index 7e2079da..1f4c1538 100644 --- a/apps/client/src/shared/lib/index.ts +++ b/apps/client/src/shared/lib/index.ts @@ -1,5 +1,5 @@ export { useSocket } from './useSocket'; export { useTheme } from './useTheme'; export { useToast } from './useToast'; -export { useTransport } from './useTransport'; +export { getRtpCapabilities, createDevice, connectTransport } from './mediasoupHelpers'; export { cn, checkDependencies } from './utils'; diff --git a/apps/client/src/shared/lib/mediasoupHelpers.ts b/apps/client/src/shared/lib/mediasoupHelpers.ts new file mode 100644 index 00000000..12850d32 --- /dev/null +++ b/apps/client/src/shared/lib/mediasoupHelpers.ts @@ -0,0 +1,73 @@ +import * as mediasoupClient from 'mediasoup-client'; +import { RtpCapabilities } from 'mediasoup-client/lib/RtpParameters'; +import { Socket } from 'socket.io-client'; +import { TransportInfo } from '../types/mediasoupTypes'; + +type ConnectTransportResponse = { + connected: boolean; + isProducer: boolean; +}; + +export const getRtpCapabilities = async (socket: Socket, roomId: string): Promise => + new Promise((resolve, reject) => { + socket.emit('getRtpCapabilities', { roomId }, (response: { rtpCapabilities: RtpCapabilities }) => { + if (response.rtpCapabilities) { + resolve(response.rtpCapabilities); + } else { + reject(new Error('getRtpCapabilities Error: RTP Capabilities를 받아오지 못했습니다.')); + } + }); + }); + +export const createDevice = async (rtpCapabilities: RtpCapabilities) => { + const newDevice = new mediasoupClient.Device(); + await newDevice.load({ + routerRtpCapabilities: rtpCapabilities, + }); + return newDevice; +}; + +export const connectTransport = async ( + socket: Socket, + device: mediasoupClient.Device, + roomId: string, + isProducer: boolean, +) => { + const transportInfo: TransportInfo = await new Promise(resolve => { + socket.emit('createTransport', { roomId, isProducer }, (response: TransportInfo) => { + resolve(response); + }); + }); + + const transport = isProducer + ? device.createSendTransport({ + id: transportInfo.transportId, + iceParameters: transportInfo.iceParameters, + iceCandidates: transportInfo.iceCandidates, + dtlsParameters: transportInfo.dtlsParameters, + }) + : device.createRecvTransport({ + id: transportInfo.transportId, + iceParameters: transportInfo.iceParameters, + iceCandidates: transportInfo.iceCandidates, + dtlsParameters: transportInfo.dtlsParameters, + }); + + transport.on('connect', async ({ dtlsParameters }, callback) => { + socket.emit( + 'connectTransport', + { + roomId, + dtlsParameters, + transportId: transportInfo.transportId, + }, + (response: ConnectTransportResponse) => { + if (response.connected) { + callback(); + } + }, + ); + }); + + return { transport, transportInfo }; +}; From d4beb60fc1acef1e6522274b747f11a4e1a826f8 Mon Sep 17 00:00:00 2001 From: zero0205 Date: Mon, 10 Feb 2025 02:24:45 +0900 Subject: [PATCH 7/8] =?UTF-8?q?:recycle:=20[Refactor]:=20SonarQube=20?= =?UTF-8?q?=EB=B6=84=EC=84=9D=EC=9C=BC=EB=A1=9C=20=EB=B0=9C=EA=B2=AC?= =?UTF-8?q?=EB=90=9C=20Reliability=20=EC=9D=B4=EC=8A=88=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Promise rejection 처리 방식 수정 (Error 객체 사용) - label 내 텍스트를 태그로 감싸서 모호한 공백 처리 문제 해결 --- apps/client/src/shared/api/axios.ts | 2 +- apps/client/src/widgets/Banner/Bookmark.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/client/src/shared/api/axios.ts b/apps/client/src/shared/api/axios.ts index 3e8a82f7..ed2abf6d 100644 --- a/apps/client/src/shared/api/axios.ts +++ b/apps/client/src/shared/api/axios.ts @@ -17,5 +17,5 @@ axiosInstance.interceptors.request.use( } return config; }, - error => Promise.reject(error), + error => Promise.reject(error instanceof Error ? error : new Error(error)), ); diff --git a/apps/client/src/widgets/Banner/Bookmark.tsx b/apps/client/src/widgets/Banner/Bookmark.tsx index 07d39ac3..0c111a18 100644 --- a/apps/client/src/widgets/Banner/Bookmark.tsx +++ b/apps/client/src/widgets/Banner/Bookmark.tsx @@ -109,7 +109,7 @@ export function Bookmark() {
-

- {errors.name && errors.name.message} -

+

{errors.name?.message}

- {/* TODO: 입력 검증 */} {/* email */}
); diff --git a/apps/client/src/shared/lib/useTransport.ts b/apps/client/src/shared/lib/useTransport.ts deleted file mode 100644 index b6aaa01e..00000000 --- a/apps/client/src/shared/lib/useTransport.ts +++ /dev/null @@ -1,98 +0,0 @@ -import * as mediasoupClient from 'mediasoup-client'; -import { useEffect, useState, useRef } from 'react'; -import { RtpCapabilities } from 'mediasoup-client/lib/RtpParameters'; -import { Device } from 'mediasoup-client/lib/types'; -import { Socket } from 'socket.io-client'; -import { checkDependencies } from '@/shared/lib/utils'; -import { TransportInfo } from '@/shared/types/mediasoupTypes'; - -type UseTransportProps = { - socket: Socket | null; - roomId: string | undefined; - isProducer: boolean; -}; - -export const useTransport = ({ socket, roomId, isProducer = false }: UseTransportProps) => { - const [transportInfo, setTransportInfo] = useState(null); - const [transportError, setTransportError] = useState(null); - const deviceRef = useRef(null); - - useEffect(() => { - if (!socket || !roomId) { - return undefined; - } - - const getRtpCapabilities = async () => { - if (!socket || !roomId) { - const dependencyError = checkDependencies('getRtpCapabilities', { socket, roomId }); - setTransportError(dependencyError); - return undefined; - } - - const rtpCapabilities: RtpCapabilities = await new Promise((resolve, reject) => { - socket.emit('getRtpCapabilities', { roomId }, (response: { rtpCapabilities: RtpCapabilities }) => { - if (response.rtpCapabilities) { - resolve(response.rtpCapabilities); - } else { - reject(new Error('getRtpCapabilities Error: RTP Capabilities를 받아오지 못했습니다.')); - } - }); - }); - return rtpCapabilities; - }; - - const createDevice = async (rtpCapabilities: RtpCapabilities) => { - if (!rtpCapabilities) { - setTransportError(new Error('createDevice Error: RTP Capabilities가 없습니다.')); - return undefined; - } - - const newDevice = new mediasoupClient.Device(); - await newDevice.load({ - routerRtpCapabilities: rtpCapabilities, - }); - return newDevice; - }; - - const createTransport = async (device: Device) => { - if (!socket || !device || !roomId) { - const dependencyError = checkDependencies('createTransport', { socket, device, roomId }); - setTransportError(dependencyError); - return; - } - - socket.emit('createTransport', { roomId, isProducer }, (response: TransportInfo) => { - setTransportInfo(response); - }); - }; - - const initializeTransport = async () => { - if (!socket || !roomId) return; - - const rtpCapabilities = await getRtpCapabilities(); - if (!rtpCapabilities) return; - - const newDevice = await createDevice(rtpCapabilities); - if (!newDevice) return; - deviceRef.current = newDevice; - - await createTransport(newDevice); - }; - - initializeTransport(); - - return () => { - if (deviceRef.current) { - deviceRef.current = null; - } - setTransportInfo(null); - setTransportError(null); - }; - }, [socket, roomId, isProducer]); - - return { - transportInfo, - device: deviceRef.current, - transportError, - }; -}; diff --git a/apps/client/src/widgets/Banner/Bookmark.tsx b/apps/client/src/widgets/Banner/Bookmark.tsx index 0c111a18..f9a80846 100644 --- a/apps/client/src/widgets/Banner/Bookmark.tsx +++ b/apps/client/src/widgets/Banner/Bookmark.tsx @@ -71,28 +71,27 @@ export function Bookmark() { <> {isLoggedIn && (
- {bookmarkList && - bookmarkList.map(data => ( -
( +
+ - -
- ))} + {data.name} + + +
+ ))} {bookmarkList.length < 5 && (