README_KO.md
August 11, 2026 ยท View on GitHub
๐ ๋ค๋ฅธ ์ธ์ด: ไธญๆ ยท English ยท ๆฅๆฌ่ช ยท Franรงais ยท Deutsch ยท ะ ัััะบะธะน ยท Espaรฑol
WeChat ์คํ์ผ์ ์ข ๋จ๊ฐ ์ํธํ ๋ฉ์ ์ . ๋ฌด์ํ ECDH + XSalsa20-Poly1305 ๋ฉ์์ง๋ณ ์ํธํ, ์ค์๊ฐ ์์ ํตํ, Cloudflare R2 ํ์ผ ์ ์ฅ, ๋ค๊ตญ์ด ์ง์ ๋ฐ iOS PWA ๋ฐฐํฌ ์ง์.
๐ธ ์คํฌ๋ฆฐ์ท (ํด๋ฆญํ์ฌ ํ์ฅ)
๊ธฐ๋ฅ
| ๊ธฐ๋ฅ | ์ค๋ช |
|---|---|
| ๐ ์ข ๋จ๊ฐ ์ํธํ | ๋ฌด์ํ ECDH + XSalsa20-Poly1305 โ ๋ฉ์์ง๋ณ ์์ ํค ์์ฑ, ์๋ฐฉํฅ ๋น๋ฐ์ฑ, Signal ๋ฐฉ์ ์์ ๋ฒํธ ๊ฒ์ฆ |
| ๐๏ธ ์ ๋ก ์ง์ ์๋ฒ | ์๋ฒ์๋ ์ํธ๋ฌธ๋ง ์ ์ฅ๋ฉ๋๋ค. ๊ฐ์ธ ํค๋ ์ ๋ ๊ธฐ๊ธฐ ๋ฐ์ผ๋ก ์ ์ถ๋์ง ์์ต๋๋ค |
| ๐น ์์ ๋ฐ ์์ฑ ํตํ | 1:1 ํตํ์ ํ์ ๋ชจ๋ LiveKit SFU ์ฌ์ฉ (์ต๋ 100๋ช ), ์ ์ฒด ์์๊ฑฐ ๋ฐ ๊ฐ์ ๋ชจ๋ |
| ๐๏ธ ์์ฑ ๋ณ์กฐ | ์์ฑ ๋ฉ์์ง, 1:1 ํตํ, ๊ทธ๋ฃน ํตํ์์ ์ค์๊ฐ ์์ฑ ํจ๊ณผ โ 3๊ฐ์ง ๋ชจ๋ (0.8x ์ ์ / 1.0x ์ผ๋ฐ / 1.2x ๊ณ ์), Web Audio API ๊ธฐ๋ฐ |
| ๐ฑ ์ธ์ ์ ์ง | 30๋ถ Access Token๊ณผ ์๋ ์ฐ์ฅ๋๋ 90์ผ ๊ธฐ๊ธฐ Refresh Session. ๋คํธ์ํฌ/IP/VPN/ํ๋ก์ ๋ณ๊ฒฝ ํ์๋ ๋น๋ฐ๋ฒํธ ์์ด ์๋ ๋ณต๊ตฌ |
| ๐จ ์ ๋ขฐ์ฑ ์๋ ๋ฉ์์ง ๋๊ธฐํ | ์๋ฐฉํฅ ํํธ๋นํธ, ๋ฐ๊ฐ๋ฐฉ ์ฐ๊ฒฐ ๊ฐ์ง, ์๊ตฌ ๋ณด๋ผ ํธ์งํจ, ๋ฉฑ๋ฑ ๋ฉ์์ง ID ๋ฐ ์๋ฒ ์ํ์ค ์ปค์ ๊ธฐ๋ฐ ๋๋ฝ ๋ณต๊ตฌ |
| ๐ด ์คํ๋ผ์ธ ์ ๊ทผ | ๊ณ์ ๋ณ๋ก ์ฐ๋ฝ์ฒ, ๊ทธ๋ฃน, ๋ํ๋น ์ต๋ 2,000๊ฐ ๋ฉ์์ง, ๋ชจ๋จผํธ, ํ์๋ผ์ธ ๋ฐ ๋ฏธ๋์ด๋ฅผ ์บ์ํ๋ฉฐ ์คํ๋ผ์ธ ์ ์ก์ ์๋ ์ฌ์๋ |
| ๐ Unicode ์น๊ตฌ ๊ฒ์ | IME ์กฐํฉ ๋ณดํธ, NFC ์ ๊ทํ ๋ฐ UTF-8 ์ฟผ๋ฆฌ ์ธ์ฝ๋ฉ์ผ๋ก ์ค๊ตญ์ด ์ฌ์ฉ์ ์ด๋ฆ๊ณผ ๋ณ๋ช ์ ์์ ์ ์ผ๋ก ๊ฒ์ |
| ๐ฅ ๊ทธ๋ฃน ์ฑํ | ์ต๋ 2000๋ช , "์ํธํ" / "๋น์ํธํ" ๋ชจ๋ ์ ํ ๊ฐ๋ฅ (๊ทธ๋ฃน ์์ ์๋ง ์ ํ ๊ฐ๋ฅ, ์ ํ ์ ์ฑํ ๊ธฐ๋ก ์ญ์ ). ์ํธํ ๋ชจ๋๋ Signal ์คํ์ผ Sender Key ํ๋กํ ์ฝ (XSalsa20-Poly1305 ๋์นญ ์ํธํ + ECDH ํค ๋ฐฐํฌ) ์ฌ์ฉ โ ๊ทธ๋ฃน ๋ฉค๋ฒ๋ง ๋ฉ์์ง ๋ณตํธํ ๊ฐ๋ฅ, ์ํธํ ๋ชจ๋์์๋ ๋ด ์ฌ์ฉ ๋ถ๊ฐ. ๋ฐฉํด ๊ธ์ง ๋ชจ๋, ๋ฉค๋ฒ ๊ด๋ฆฌ |
| ๐ซ ์น๊ตฌ ์์คํ | ์น๊ตฌ ์์ฒญ ์ ์ต๋ 512์ ๋ฉ์์ง์ ํจ๊ป ์น์ธ ํ์; ์ฌ์ฉ์ ์ง์ ๋ณ๋ช ; ๋ค์ค ํ๊ทธ ๋ถ๋ฅ |
| โฑ๏ธ ๋ฉ์์ง ์๋ ์ญ์ | 5๋จ๊ณ (์์ / 1์ผ / 3์ผ / 1์ฃผ์ผ / 1๊ฐ์), DM์์๋ ์์ชฝ ๋ชจ๋ ์ค์ ๊ฐ๋ฅ, ๊ทธ๋ฃน์์๋ ์์ ์๋ง ์ค์ ๊ฐ๋ฅ |
| ๐ ํธ์ ์๋ฆผ | Web Push (VAPID) + FCM + OneSignal + ntfy + APNS 5์ฑ๋ โ ์คํ๋ผ์ธ ์ฌ์ฉ์์๊ฒ๋ ์ ๋ฌ (iOS ๋ค์ดํฐ๋ธ + Google ์๋น์ค ์๋ ์ค๊ตญ์ฐ Android ์ง์) |
| ๐ ๋ค๊ตญ์ด ์ง์ | ์ค๊ตญ์ด, ์์ด, ์ผ๋ณธ์ด, ํ๊ตญ์ด, ํ๋์ค์ด, ๋ ์ผ์ด, ๋ฌ์์์ด, ์คํ์ธ์ด โ ์๋ ๊ฐ์ง + ์๋ ์ ํ |
| ๐ฑ iOS โ ๊ธฐ์ ์ธ์ฆ์ ๋ถํ์ | Safari์์ "ํ ํ๋ฉด์ ์ถ๊ฐ" PWA ๋ฐฉ์์ผ๋ก Apple ์๋ช ์์ด ์๊ตฌ์ ์ผ๋ก ์ฌ์ฉ ๊ฐ๋ฅ |
| ๐ฑ Android ๋ค์ดํฐ๋ธ ์ฑ | Google Play์์ ๋ค์ด๋ก๋ ๊ฐ๋ฅ, FCM ํธ์ ์๋ฆผ ์ง์ |
| ๐ฑ iOS ๋ค์ดํฐ๋ธ ์ฑ | App Store์์ ๋ค์ด๋ก๋ ๊ฐ๋ฅ, APNS ํธ์ ์๋ฆผ ์ง์ |
| ๐ฅ๏ธ Windows ๋ฐ์คํฌํฑ ํด๋ผ์ด์ธํธ | Windows ๋ค์ดํฐ๋ธ ๋ฐ์คํฌํฑ ์ฑ, ์ฌ๊ธฐ์์ ๋ค์ด๋ก๋ |
| ๐ Mac ๋ฐ์คํฌํฑ ํด๋ผ์ด์ธํธ | Mac ๋ค์ดํฐ๋ธ ๋ฐ์คํฌํฑ ์ฑ, ์ฌ๊ธฐ์์ ๋ค์ด๋ก๋ |
| ๐ฌ ๋ฆฌ์น ๋ฉ์์ง | ํ ์คํธ, ์ด๋ฏธ์ง, ์์, ๋ฌธ์ ํ์ผ, ์์ฑ ๋ฉ์์ง, 200+ ์ด๋ชจ์ง, Telegram ์คํฐ์ปค ํฉ, ์์ ํ์ธ, ์ ๋ ฅ ์ค ํ์ |
| ๐ค ํ์ผ ์ ๋ก๋ | ํ์ผ๋น ์ต๋ 500MB, Cloudflare R2 ๋๋ ๋ก์ปฌ ์ ์ฅ์, ์งํ๋ฅ ์ ๋๋ฉ์ด์ |
| ๐ ๋ชจ๋จผํธ | WeChat ์คํ์ผ ์์ ํผ๋: ํ ์คํธ + ์ต๋ 9์ฅ ์ฌ์ง ๋๋ 1๊ฐ ์์ (โค 10๋ถ), ์ข์์, ๋๊ธ, ํ๊ทธ ๊ธฐ๋ฐ ๊ณต๊ฐ ๋ฒ์ ์ค์ |
| ๐ค ์ฌ์ฉ์ ํ๋กํ | ์ฐ๋ฝ์ฒ ํ๋กํ ํ์ด์ง, ์๋ฐฉํฅ ๋ชจ๋จผํธ ๊ฐ์ธ์ ๋ณด ์ ์ด |
| ๐ฐ ํ์๋ผ์ธ | ์ค์คํ์ ์คํ์ผ ๊ณต๊ฐ ํผ๋ โ 2์ด ๋งค๊ฑฐ์ง ๋ ์ด์์, ์ต๋ช ๊ฒ์, ์ข์์ ๋ฐ ๋๊ธ |
| ๐ท๏ธ ์น๊ตฌ ํ๊ทธ | ์น๊ตฌ์๊ฒ ์ฌ๋ฌ ํ๊ทธ ํ ๋น (12์ ํ๋ ํธ), ํ๊ทธ๋ณ ์ฐ๋ฝ์ฒ ํํฐ๋ง |
| ๐๏ธ R2 ์ค๋ธ์ ํธ ์คํ ๋ฆฌ์ง | ์ด๋ฏธ์ง/์์ฑ ํ์ผ์ฉ Cloudflare R2 โ ์ ํ์ ๊ณต๊ฐ CDN URL |
| ๐ 2๋จ๊ณ ์ธ์ฆ (2FA) | Google Authenticator ํธํ TOTP, 8๊ฐ ๋ณต๊ตฌ ์ฝ๋, ๋ก๊ทธ์ธ ์ ์ ์ฉ |
| ๐ท QR ์ฝ๋ ์ค์บ ๋ฐ ๊ณต์ | QR ์ฝ๋๋ก ์น๊ตฌ ์ถ๊ฐ ๋๋ ๊ทธ๋ฃน ์ฐธ์ฌ, ๋ง๋ฃ ๊ธฐ๊ฐ ์ค์ ๊ฐ๋ฅ |
| ๐๏ธ ์ ํ ํธ์คํ | Docker Compose, Zeabur ์ํด๋ฆญ, ๋๋ Vercel์ ํ๋ก ํธ์๋ ๋ฐฐํฌ |
| ๐ ํ๋ก์ ์ค์ | SOCKS5 / HTTP / HTTPS ํ๋ก์ ์ง์ โ ๋ก๊ทธ์ธ ๋ฐ ์ค์ ํ์ด์ง์์ ์๋ฒ ์ฃผ์, ํฌํธ, ์ฌ์ฉ์ ์ด๋ฆ, ๋น๋ฐ๋ฒํธ ์ค์ ๊ฐ๋ฅ |
| ๐ก๏ธ ์ฝํ ์ธ ๊ด๋ฆฌ | ์ฌ์ฉ์ ์ ๊ณ (6๊ฐ์ง ์นดํ ๊ณ ๋ฆฌ) + ์ฌ์ฉ์ ์ฐจ๋จ (๊ฒ์๋ฌผ/๋ฉ์์ง ์ฆ์ ์จ๊น) + ์ด์ฉ์ฝ๊ด (EULA) |
| ๐ง ๊ด๋ฆฌ์ ํจ๋ | ๋ด์ฅ ์น ๊ด๋ฆฌ ๋์๋ณด๋ (/admin, ๊ฒฝ๋ก ๋ณ๊ฒฝ ๊ฐ๋ฅ), ๋น๋ฐ๋ฒํธ ๋ณดํธ, ์ ๊ณ ๊ฒํ , ์ฝํ
์ธ ์ญ์ , ์ฌ์ฉ์ ์ฐจ๋จ โ 8๊ฐ ์ธ์ด ์ง์ |
v2.3.9 ์ ๋ฐ์ดํธ
- ๊ธฐ์กด ๋จ๋ฐฉํฅ ์น๊ตฌ ๊ด๊ณ ๋ฐ์ดํฐ ๋๋ฌธ์ โ์ด๋ฏธ ์น๊ตฌ์ ๋๋คโ๋ผ๊ณ ํ์๋์ง๋ง ์ฐ๋ฝ์ฒ์ ๋ํ๋์ง ์๊ณ ์ฑํ ํ ์ ์๋ ๋ฌธ์ ๋ฅผ ์์ ํ์ต๋๋ค. ์ฌ์ฉ์๋ฅผ ๋ค์ ์ถ๊ฐํ๋ฉด ์๋ฐฉํฅ ๊ด๊ณ๋ฅผ ์๋์ผ๋ก ๋ณต๊ตฌํ๊ณ ์ฐ๋ฝ์ฒ ๋ชฉ๋ก์ ์ฆ์ ์๋ก ๊ณ ์นฉ๋๋ค.
v2.3.8 ์ ๋ฐ์ดํธ
- QR ์ค์บ๋ ์นด๋ฉ๋ผ ์์ ํ ๋ค๋ก ๊ฐ๊ธฐ ๋ฒํผ์ด ์๋ํ์ง ์๋ ๋ฌธ์ ๋ฅผ ์์ ํ์ผ๋ฉฐ, ๋ซ์ ๋ ์นด๋ฉ๋ผ๋ฅผ ์ฆ์ ์ค์งํ๊ณ ํด์ ํฉ๋๋ค.
- ๊ธฐ์กด ์น๊ตฌ์๊ฒ ์ค๋ณต ์์ฒญ์ ๋ณด๋ด๋ฉด ์น๊ตฌ ๊ด๊ณ๊ฐ ์์๋๋ ๋ฌธ์ ๋ฅผ ์์ ํ์ผ๋ฉฐ, ๊ฒ์ ๊ฒฐ๊ณผ์ โ์ด๋ฏธ ์น๊ตฌ์ ๋๋คโ๊ฐ ํ์๋ฉ๋๋ค.
- ์ข ๋จ๊ฐ ์ํธํ ์งํ ๋ฐ์ ๊ฐ์ธ ๋ฉ์์ง์ ๋๊ด์ ๊ฐ์ฒด์ ์ํธ๋ฌธ์ ๊ธฐ๋กํ์ฌ ์๋ฒ ํ์ธ์ ๊ธฐ๋ค๋ฆฌ๋ ๋์ ํ๋ฌธ์ด ์ ์ ์ ์ฅ๋์ง ์๋๋ก ํ์ต๋๋ค.
- ์์ฑ ๋ฉ์์ง๋ ์ต๋ 120์ด์์ ์๋ ์ค์ง๋๋ฉฐ ์์ฑ ๋ณ์กฐ ๊ฒฐ๊ณผ์๋ ๊ฐ์ ์ ํ์ ์ ์ฉํฉ๋๋ค.
- ๋ น์๊ณผ ํตํ ์ค ํ๋ฉด์ ์ผ ๋๊ณ ํ์ด์ง๋ฅผ ๋ ๋ ๋ ๋ น์ ์ฅ์น์ ํ์ด๋จธ๋ฅผ ํ์คํ ํด์ ํฉ๋๋ค.
- Android๋ Android Keystore์ AES-256-GCM์ผ๋ก ํค์ ์บ์๋ ๋ณดํธํฉ๋๋ค. Web ํด๋ผ์ด์ธํธ๋ ๋ธ๋ผ์ฐ์ ์ ์ฅ์๋ฅผ ์ ์งํฉ๋๋ค.
์ธ์ ๋ณต๊ตฌ์ ๋ฉ์์ง ์ ๋ขฐ์ฑ
PaperPhonePlus๋ ๋ก์ปฌ ๊ณ์ ์ํ, ์ค์๊ฐ ์ฐ๊ฒฐ ์ํ, ๋ฉ์์ง ๋๊ธฐํ ์ํ๋ฅผ ๋ถ๋ฆฌํ์ฌ ์ฒ๋ฆฌํฉ๋๋ค. WebSocket์ด ์ด๋ ค ์์ด๋ ์๋ฒ์ auth_ok๋ฅผ ๋ฐ๊ธฐ ์ ์๋ ์ฌ์ฉ ๊ฐ๋ฅํ ์ฐ๊ฒฐ๋ก ๊ฐ์ฃผํ์ง ์์ต๋๋ค. ์๋ฐฉํฅ ping/pong์ผ๋ก VPN/IP ๋ณ๊ฒฝ, Wi-Fi/๋ชจ๋ฐ์ผ ์ ํ, ์ฑ ์ผ์ ์ค๋จ์ผ๋ก ์๊ธฐ๋ ๋ฐ๊ฐ๋ฐฉ ์ฐ๊ฒฐ์ ๊ฐ์งํฉ๋๋ค.
- Access Token์ 30๋ถ ๋์ ์ ํจํฉ๋๋ค. ๊ธฐ๊ธฐ Refresh Token์ 90์ผ ๋์ ์ ํจํ๋ฉฐ ํ๋ฐํ ์ฌ์ฉํ๋ ๋์ ์ฐ์ฅ๋์ด ๋น๋ฐ๋ฒํธ ์ฌ์ ๋ ฅ ์์ด ๊ฐฑ์ ๋ฉ๋๋ค.
- ์ด์ ๋ฒ์ ์์ ์ด๋ฏธ ๋ก๊ทธ์ธํ ๊ธฐ๊ธฐ๋ ๊ธฐ์กด Token์ด ์ ํจํ ๋์ ์๋ ์ ๊ทธ๋ ์ด๋๋ฉ๋๋ค. ์ด๋ฏธ ๋ง๋ฃ๋ ๊ฒฝ์ฐ์๋ง ๋ง์ง๋ง์ผ๋ก ํ ๋ฒ ์ง์ ๋ก๊ทธ์ธํด์ผ ํฉ๋๋ค.
- ๋ชจ๋ ๋ฐ์ ๋ฉ์์ง์๋ ์์ ์ ์ธ
client_msg_id๊ฐ ์์ต๋๋ค. ์๋ฒ ACK๋ฅผ ๋ฐ์ง ๋ชปํ ๋ฉ์์ง๋ ์๊ตฌ ๋ก์ปฌ ๋ณด๋ผ ํธ์งํจ์ ๋จ์ ๊ฐ์ ID๋ก ์ฌ์ ์ก๋๋ฉฐ, ์๋ฒ ๊ณ ์ ์ ์ฝ ์กฐ๊ฑด์ด ์ค๋ณต ์ ์ฅ์ ๋ฐฉ์งํฉ๋๋ค. - ์ ์ฅ๋ ๋ฉ์์ง์๋ ๋จ์กฐ ์ฆ๊ฐํ๋
server_seq๊ฐ ์์ต๋๋ค. ์ธ์ฆ, ์ฌ์ฐ๊ฒฐ, ํฌ๊ทธ๋ผ์ด๋ ๋ณต๊ท ํ ์ปค์ ๊ธฐ๋ฐ์ผ๋ก ๋๋ฝ๋ ๋ฉ์์ง๋ฅผ ๋๊ธฐํํฉ๋๋ค. - ๋ช ์์ ๋ก๊ทธ์์๊ณผ ๊ธฐ๊ธฐ ์ทจ์๋ ์ฅ๊ธฐ ์๋ฒ ์ธ์ ์ ๋ฌดํจํํฉ๋๋ค. ์ผ๋ฐ์ ์ธ ์ ์ก ์ค๋ฅ์ IP ๋ณ๊ฒฝ์ ๋ก๊ทธ์ธ ์ํ๋ฅผ ์ ์งํฉ๋๋ค.
Important
์ ๊ทธ๋ ์ด๋ ์ server๋ฅผ ๋จผ์ ๋ฐฐํฌํ๊ณ client๋ฅผ ๋์ค์ ๋ฐฐํฌํ์ธ์. server๋ ์ ๋ขฐ์ฑ DB ๋ง์ด๊ทธ๋ ์ด์ ์ ์๋ ์ ์ฉยท๊ฒ์ฆํ๋ฉฐ ํ์ ์ด์ด ์์ผ๋ฉด ์์ํ์ง ์์ต๋๋ค. ํ๋ก๋์ ์ ๊ทธ๋ ์ด๋ ์ ์ MySQL์ ๋ฐฑ์ ํ์ธ์.
๊ธฐ์ ์คํ
๋ฐฑ์๋ (server/)
Rust (Axum 0.8) โ ๊ณ ์ฑ๋ฅ ๋น๋๊ธฐ ์น ํ๋ ์์ํฌ
sqlx + MySQL 8.0 โ ์ฌ์ฉ์/๋ฉ์์ง ์์ํ
deadpool-redis + Redis 7 โ ์จ๋ผ์ธ ์ํ + ํฌ๋ก์ค ๋
ธ๋ ๋ผ์ฐํ
aws-sdk-s3 โ Cloudflare R2 ํ์ผ ์ ์ฅ์ (S3 ํธํ API)
argon2 + jsonwebtoken ์ธ์ฆ
ํ๋ก ํธ์๋ (client/)
React 19 + TypeScript + Vite 6
Zustand ์ํ ๊ด๋ฆฌ
libsodium-wrappers-sumo (WebAssembly โ Curve25519 / XSalsa20-Poly1305)
WebRTC API โ ์์/์์ฑ ํตํ
Web Audio API โ ์ค์๊ฐ ์์ฑ ๋ณ์กฐ (ScriptProcessorNode ์ค๋์ค ์ฒด์ธ)
PWA: manifest.json + Service Worker
์ํธํ ๋ ์ด์ด
๋ฌด์ํ ECDH + XSalsa20-Poly1305 โ ๋ฉ์์ง๋ณ ์์ ํคํ์ด ์์ฑ
4๋จ๊ณ ํค ์ ์ฅ: memory โ localStorage โ sessionStorage โ IndexedDB
๋ชจ๋ ๊ฐ์ธ ํค๋ ๊ธฐ๊ธฐ์๋ง ์ ์ฅ๋ฉ๋๋ค โ ์๋ฒ๋ก ์ ์ก๋์ง ์์ต๋๋ค
๐ ์์ธ ๋ฐฐํฌ ๊ฐ์ด๋ (ไธญๆ) | Deployment Guide (English) โ Zeabur + Vercel ํ์ด๋ธ๋ฆฌ๋ ๋ฐฐํฌ, Docker Compose + Nginx ๋ก์ปฌ ๋ฐฐํฌ ๋ฐ ํด๋ผ์ด์ธํธ ์๋ฒ ์ฃผ์ ๊ตฌ์ฑ์ ๋ํ ๋จ๊ณ๋ณ ์ ์ฒด ๊ฐ์ด๋.
์ต์ 0: Zeabur ์ํด๋ฆญ ํด๋ผ์ฐ๋ ๋ฐฐํฌ
Zeabur ํตํ ๋คํธ์ํฌ ์ ํ: ํ ํ๋ฆฟ์ WebSocket/API 7880๊ณผ ICE/TCP 7881๋ก LiveKit์ ๋ฐฐํฌํฉ๋๋ค. ํ์ฌ Zeabur๋ UDP ์๋น์ค ํฌํธ๋ฅผ ๋ ธ์ถํ์ง ์์ผ๋ฏ๋ก 1:1 ํตํ์ ํ์๋ TCP ํด๋ฐฑ์ ์ฌ์ฉํฉ๋๋ค. ํ๋ก๋์ ํตํ์๋ LiveKit Cloud ๋๋ UDP๋ฅผ ์ง์ํ๋ VM์ ์ฌ์ฉํ์ญ์์ค.
์๋ฒ Nginx ๊ตฌ์ฑ
๋ ๋๋ฉ์ธ์ฉ ํ๋ก๋์
์ค์ deploy/nginx/paperphone-plus.conf์ ์ฌ์ฉํ์ญ์์ค. api.example.com๊ณผ meeting.example.com์ ์ค์ ๋๋ฉ์ธ์ผ๋ก ๋ฐ๊พธ๊ณ TLS ์ธ์ฆ์๋ฅผ ๋ฐ๊ธํ ๋ค ํ์ผ์ /etc/nginx/sites-available/paperphone-plus์ ๋ณต์ฌํ์ฌ ํ์ฑํํ๊ณ sudo nginx -t && sudo systemctl reload nginx๋ฅผ ์คํํฉ๋๋ค. ๋ฐฑ์๋์๋ LIVEKIT_URL=wss://meeting.example.com์ ์ค์ ํฉ๋๋ค. Nginx๋ API์ WebSocket๋ง ํ๋ก์ํ๋ฏ๋ก TCP 7881๊ณผ UDP 7882๋ ํธ์คํธ ๋ฐ ํด๋ผ์ฐ๋ ๋ฐฉํ๋ฒฝ์์ ์ง์ ๊ฐ๋ฐฉํด์ผ ํฉ๋๋ค.
Tip
๊ณ ๊ธ: Zeabur + Vercel ํ์ด๋ธ๋ฆฌ๋ ๋ฐฐํฌ Zeabur์ ๋ฐฐํฌํ ํ client ์๋น์ค๋ฅผ ์๋์ผ๋ก ์ญ์ ํ๊ณ ํ๋ก ํธ์๋๋ฅผ Vercel์ ๋ฐฐํฌํ ์ ์์ต๋๋ค (์๋ ์ต์ 2 ์ฐธ์กฐ). ์ด๋ ๊ฒ ํ๋ฉด server/MySQL/Redis๋ Zeabur์์ ํธ์คํ ๋๊ณ ํ๋ก ํธ์๋๋ Vercel์ ๊ธ๋ก๋ฒ CDN์ผ๋ก ๊ฐ์๋ฉ๋๋ค. ํ๋ก ํธ์๋์๋ Vercel์์ ํ๊ฒฝ ๋ณ์๊ฐ ํ์ ์์ต๋๋ค โ ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ ํ์ด์ง์์ ๋ฐฑ์๋ ์๋ฒ ์ฃผ์๋ฅผ ์ ๋ ฅํ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
์ต์ 1: Docker Compose (๊ถ์ฅ)
git clone <repo-url> && cd paperphone-plus
cp server/.env.example server/.env
# ํธ์ง: DB_PASS / JWT_SECRET / LIVEKIT_URL ๋ฑ
docker compose up -d
open http://localhost
์ต์ 2: Vercel์ ํ๋ก ํธ์๋ ๋ฐฐํฌ
# 1. ์ด ์ ์ฅ์๋ฅผ ํฌํฌํฉ๋๋ค
# 2. Vercel์์ ๊ฐ์ ธ์ค๊ธฐ: Root Directory = client/, Build = npm run build, Output = dist/
# ํ๊ฒฝ ๋ณ์ ์ค์ ์ด ํ์ ์์ต๋๋ค
# 3. Docker ๋๋ Zeabur๋ฅผ ํตํด ๋ฐฑ์๋๋ฅผ ๋ฐฐํฌํฉ๋๋ค
# 4. Vercel์ ๋ฐฐํฌ๋ ํ๋ก ํธ์๋๋ฅผ ์ด๊ณ ๋ก๊ทธ์ธ ํ์ด์ง์์ ๋ฐฑ์๋ ์๋ฒ ์ฃผ์๋ฅผ ์
๋ ฅํฉ๋๋ค
# ์: https://your-server.zeabur.app
์ต์ 3: ๋ก์ปฌ ๊ฐ๋ฐ
# ๋ฐฑ์๋ (Rust)
cd server && cp .env.example .env && cargo run --release
# ํ๋ก ํธ์๋ (React)
cd client && npm install && npm run dev
์์ฑ ๋ณ์กฐ
์์ฑ ๋ฉ์์ง, 1:1 ํตํ, ๊ทธ๋ฃน ํตํ ๋ชจ๋ ์ค์๊ฐ ์์ฑ ๋ณ์กฐ๋ฅผ ์ง์ํ๋ฉฐ 3๊ฐ์ง ๋ชจ๋๋ฅผ ์ ํํ ์ ์์ต๋๋ค:
| ๋ชจ๋ | ์๋ | ํจ๊ณผ |
|---|---|---|
| ๐ข ๋๋ฆฌ๊ฒ | 0.8x | ๋ ๊น๊ณ ๋ฎ์ ์์ฑ โ ์ต๋ช ์ฑ์ ์ ํฉ |
| ๐ ์ผ๋ฐ | 1.0x | ์๋ ์์ฑ, ์ฒ๋ฆฌ ์์ |
| ๐ ๋น ๋ฅด๊ฒ | 1.2x | ๋ ๋์ ์์ฑ โ ์ฌ๋ฏธ์๊ณ ์ฅ๋์ค๋ฌ์ด ํจ๊ณผ |
์๋ ์๋ฆฌ: Web Audio API๋ฅผ ์ฌ์ฉํ์ฌ ์ค๋์ค ์ฒ๋ฆฌ ์ฒด์ธ (AudioContext โ MediaStreamSource โ ScriptProcessorNode โ MediaStreamDestination)์ ๊ตฌ์ถํ๊ณ ๋ง์ดํฌ ์ ๋ ฅ์ ํผ์น/์๋๋ฅผ ์ค์๊ฐ์ผ๋ก ์กฐ์ ํฉ๋๋ค.
- ์์ฑ ๋ฉ์์ง: ๋
น์ ์ค ์์ฑ ๋ชจ๋๋ฅผ ์ ํํฉ๋๋ค. ๋ด๋ณด๋ด์ง
.webmํ์ผ์๋ ์ด๋ฏธ ์์ฑ ํจ๊ณผ๊ฐ ํฌํจ๋์ด ์์ด ์์ ์๊ฐ ์๋ ์์ฑ์ ๋ณต์ํ ์ ์์ผ๋ฉฐ, ์ง์ ํ ์ต๋ช ๋ฉ์์ง๊ฐ ๊ฐ๋ฅํฉ๋๋ค - 1:1 / ๊ทธ๋ฃน ํตํ: ํตํ ์ค ์์ฑ ๋ณ์กฐ ๋ฒํผ์ ํญํ์ฌ ๋ชจ๋๋ฅผ ์ํํฉ๋๋ค. ์ฒ๋ฆฌ๋ ์ค๋์ค ํธ๋์ด
LiveKit LocalAudioTrack.replaceTrack()์ ํตํด ์๋ ํธ๋์ ๋์ฒดํฉ๋๋ค
์๋ฒ ์ธก ์ค์ ์ด ํ์ ์์ต๋๋ค. ์์ฑ ๋ณ์กฐ๋ ์ ์ ์ผ๋ก ํด๋ผ์ด์ธํธ ์ธก์์ ์คํ๋ฉ๋๋ค.
ํ๊ฒฝ ๋ณ์
| ๋ณ์ | ์ค๋ช | ๊ธฐ๋ณธ๊ฐ |
|---|---|---|
PORT | ์๋ฒ ํฌํธ | 3000 |
JWT_SECRET | JWT ์๋ช ํค (ํ๋ก๋์ ์์ ๋ฐ๋์ ๋ณ๊ฒฝํ์ธ์) | dev_secret |
DB_HOST / DB_PASS / DB_NAME | MySQL ์ฐ๊ฒฐ ์ ๋ณด | โ |
REDIS_HOST / REDIS_PASS | Redis ์ฐ๊ฒฐ ์ ๋ณด | โ |
R2_ACCOUNT_ID | Cloudflare ๊ณ์ ID | โ |
R2_ACCESS_KEY_ID | R2 API ํ ํฐ ์ก์ธ์ค ํค | โ |
R2_SECRET_ACCESS_KEY | R2 API ํ ํฐ ์ํฌ๋ฆฟ ํค | โ |
R2_BUCKET | R2 ๋ฒํท ์ด๋ฆ | โ |
R2_PUBLIC_URL | R2 ๊ณต๊ฐ ๊ธฐ๋ณธ URL (์ ํ ์ฌํญ) | โ |
LIVEKIT_URL | ๋ชจ๋ ํตํ์ ์ฌ์ฉํ๋ ๊ณต๊ฐ LiveKit WebSocket URL | โ |
LIVEKIT_API_KEY | ์๋ฒ์ LiveKit์ด ๊ณต์ ํ๋ API Key | โ |
LIVEKIT_API_SECRET | ์๋ฒ์ LiveKit์ด ๊ณต์ ํ๋ API Secret | โ |
VAPID_PUBLIC_KEY | Web Push VAPID ๊ณต๊ฐ ํค (์ ํ ์ฌํญ) | โ |
VAPID_PRIVATE_KEY | Web Push VAPID ๊ฐ์ธ ํค (์ ํ ์ฌํญ) | โ |
VAPID_SUBJECT | VAPID ์ฐ๋ฝ์ฒ ์ด๋ฉ์ผ (์ ํ ์ฌํญ) | mailto:admin@paperphoneplus.app |
FCM_PROJECT_ID | Firebase ํ๋ก์ ํธ ID (์ ํ ์ฌํญ, Capacitor Android) | โ |
FCM_CLIENT_EMAIL | Firebase ์๋น์ค ๊ณ์ ์ด๋ฉ์ผ (์ ํ ์ฌํญ) | โ |
FCM_PRIVATE_KEY | Firebase ์๋น์ค ๊ณ์ ๊ฐ์ธ ํค (์ ํ ์ฌํญ, \n ์ด์ค์ผ์ดํ ๋ฐ ์ค์ ์ค๋ฐ๊ฟ ๋ชจ๋ ์ง์; ์๋ ์ฐธ์กฐ) | โ |
FCM_RELAY_SECRET | FCM ํธ์ ๋ฆด๋ ์ด ์ํฌ๋ฆฟ (์ ํ ์ฌํญ, ๋ฆด๋ ์ด ํธ์คํธ์ ์ค์ ํ์ฌ ์๋ํฌ์ธํธ ํ์ฑํ) | โ |
FCM_RELAY_URL | FCM ํธ์ ๋ฆด๋ ์ด URL (์ ํ ์ฌํญ, ์ ํ ํธ์คํ ์๋ฒ์์ ๋ฆด๋ ์ด ํธ์คํธ๋ก ์ฐ๊ฒฐ) | โ |
FCM_RELAY_KEY | FCM ํธ์ ๋ฆด๋ ์ด ์ธ์ฆ ํค (์ ํ ์ฌํญ, ๋ฆด๋ ์ด ํธ์คํธ์ FCM_RELAY_SECRET๊ณผ ์ผ์นํด์ผ ํฉ๋๋ค) | โ |
ONESIGNAL_APP_ID | OneSignal App ID (์ ํ ์ฌํญ) | โ |
ONESIGNAL_REST_KEY | OneSignal REST API Key (์ ํ ์ฌํญ) | โ |
ONESIGNAL_RELAY_SECRET | OneSignal ํธ์ ๋ฆด๋ ์ด ์ํฌ๋ฆฟ (์ ํ ์ฌํญ, ๋ฆด๋ ์ด ํธ์คํธ์ ์ค์ ํ์ฌ ์๋ํฌ์ธํธ ํ์ฑํ) | โ |
ONESIGNAL_RELAY_URL | OneSignal ํธ์ ๋ฆด๋ ์ด URL (์ ํ ์ฌํญ, ์ ํ ํธ์คํ ์๋ฒ์์ ๋ฆด๋ ์ด ํธ์คํธ๋ก ์ฐ๊ฒฐ) | โ |
ONESIGNAL_RELAY_KEY | OneSignal ํธ์ ๋ฆด๋ ์ด ์ธ์ฆ ํค (์ ํ ์ฌํญ, ๋ฆด๋ ์ด ํธ์คํธ์ ONESIGNAL_RELAY_SECRET๊ณผ ์ผ์นํด์ผ ํฉ๋๋ค) | โ |
NTFY_BASE_URL | ntfy ์๋ฒ URL (์ ํ ์ฌํญ, ๊ธฐ๋ณธ์ ์ผ๋ก ๊ณต๊ฐ ntfy.sh ์ฌ์ฉ) | https://ntfy.sh |
NTFY_TOKEN | ntfy ์ธ์ฆ ํ ํฐ (์ ํ ์ฌํญ, ์ ํ ํธ์คํ ์๋ฒ์ฉ) | โ |
APNS_TEAM_ID | Apple Developer Team ID (์ ํ ์ฌํญ, iOS ๋ค์ดํฐ๋ธ ํธ์) | โ |
APNS_KEY_ID | APNS ์ธ์ฆ ํค ID (์ ํ ์ฌํญ) | โ |
APNS_PRIVATE_KEY | APNS .p8 ๊ฐ์ธ ํค ๋ด์ฉ (์ ํ ์ฌํญ, \n ์ด์ค์ผ์ดํ ์ง์) | โ |
APNS_BUNDLE_ID | iOS ์ฑ Bundle Identifier (์ ํ ์ฌํญ) | โ |
APNS_SANDBOX | APNS ์๋๋ฐ์ค ๋ชจ๋ (์ ํ ์ฌํญ, ๊ฐ๋ฐ/TestFlight์ฉ true) | false |
APNS_RELAY_SECRET | ํธ์ ๋ฆด๋ ์ด ์ํฌ๋ฆฟ (์ ํ ์ฌํญ, ๋ฆด๋ ์ด ํธ์คํธ์ ์ค์ ํ์ฌ ์๋ํฌ์ธํธ ํ์ฑํ) | โ |
APNS_RELAY_URL | ํธ์ ๋ฆด๋ ์ด URL (์ ํ ์ฌํญ, ์ ํ ํธ์คํ ์๋ฒ์์ ๋ฆด๋ ์ด ํธ์คํธ๋ก ์ฐ๊ฒฐ) | โ |
APNS_RELAY_KEY | ํธ์ ๋ฆด๋ ์ด ์ธ์ฆ ํค (์ ํ ์ฌํญ, ๋ฆด๋ ์ด ํธ์คํธ์ APNS_RELAY_SECRET๊ณผ ์ผ์นํด์ผ ํฉ๋๋ค) | โ |
TELEGRAM_BOT_TOKEN | Telegram Bot Token (์ ํ ์ฌํญ) | โ |
STICKER_PACKS | ์ปค์คํ
์คํฐ์ปค ํฉ (์ ํ ์ฌํญ, name:label) | ๊ธฐ๋ณธ 12๊ฐ ๋ด์ฅ |
ADMIN_PATH | ๊ด๋ฆฌ์ ํจ๋ URL ๊ฒฝ๋ก | /admin |
ADMIN_PASSWORD | ๊ด๋ฆฌ์ ํจ๋ ๋น๋ฐ๋ฒํธ (ํ๋ก๋์ ์์ ๋ฐ๋์ ๋ณ๊ฒฝํ์ธ์) | admin123 |
FCM ๊ฐ์ธ ํค ์ค๋ฐ๊ฟ ์ฒ๋ฆฌ
Firebase ์๋น์ค ๊ณ์ JSON์ private_key ํ๋์๋ PEM ํ์์ RSA ๊ฐ์ธ ํค๊ฐ ํฌํจ๋์ด ์์ผ๋ฉฐ, ๊ฐ 64์ ์ค ์ฌ์ด์ ์ค์ ์ค๋ฐ๊ฟ ๋ฌธ์ (\n, ASCII 0x0A)๊ฐ ํ์ํฉ๋๋ค. ๊ทธ๋ฌ๋ ๋ง์ ๋ฐฐํฌ ํ๋ซํผ (Zeabur, Vercel, Railway, Docker)์ ํ๊ฒฝ ๋ณ์๋ฅผ ๋จ์ผ ํ ๋ฌธ์์ด๋ก ์ ์ฅํ์ฌ \n์ ๋ฆฌํฐ๋ด ๋ ๋ฌธ์ ์ํ์ค \ + n์ผ๋ก ๋ณํํฉ๋๋ค.
์ด๊ฒ์ด FCM ํธ์ ์๋ฆผ ์คํจ์ ๊ฐ์ฅ ํํ ์์ธ์ ๋๋ค โ PEM ํ์๊ฐ ์กฐ์ฉํ ์คํจํ๊ณ ํธ์ ์๋ฆผ์ด ์ ์ก๋์ง ์์ผ๋ฉฐ, ์ค๋ฅ ๋ก๊ทธ๋ ๋จ์ง ์์ต๋๋ค.
์๋ฒ๊ฐ ์ด๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค: fcm.rs๊ฐ ๋ฆฌํฐ๋ด \n ์ํ์ค๋ฅผ ํ์ฑ ์ ์ ์ค์ ์ค๋ฐ๊ฟ์ผ๋ก ์ ๊ทํํฉ๋๋ค. ๋ ๊ฐ์ง ํ์ ๋ชจ๋ ์๋ํฉ๋๋ค:
-
๋จ์ผ ํ (ํด๋ผ์ฐ๋ ํ๋ซํผ ๊ถ์ฅ): JSON ํ์ผ์
private_key๊ฐ์\n์ด์ค์ผ์ดํ์ ํจ๊ป ๊ทธ๋๋ก ๋ถ์ฌ๋ฃ์ผ์ธ์:FCM_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\nMIIEvQ...\n-----END PRIVATE KEY-----\n -
์ฌ๋ฌ ํ (.env ํ์ผ์ฉ): ์ ์ฒด PEM ๋ด์ฉ์ ๋ฐ์ดํ๋ก ๊ฐ์ธ๊ณ ์ค์ ์ค๋ฐ๊ฟ์ ์ฌ์ฉํ์ธ์:
FCM_PRIVATE_KEY="-----BEGIN PRIVATE KEY----- MIIEvQ... -----END PRIVATE KEY-----"
| ํ๋ซํผ | ๊ถ์ฅ ํ์ | ๋น๊ณ |
|---|---|---|
| Zeabur | ๋จ์ผ ํ (\n ์ด์ค์ผ์ดํ) | Variables ํจ๋์ JSON ๊ฐ์ ์ง์ ๋ถ์ฌ๋ฃ๊ธฐ |
| Docker / docker-compose | ๋ ๋ค ๊ฐ๋ฅ | ์ฌ๋ฌ ํ์ YAML | ์ฌ์ฉ; ๋จ์ผ ํ์ .env์์ |
| Vercel / Railway | ๋จ์ผ ํ (\n ์ด์ค์ผ์ดํ) | ์ ๋ ฅ ํ๋๋ ์ผ๋ฐ์ ์ผ๋ก ์ค์ ์ค๋ฐ๊ฟ์ ์ง์ํ์ง ์์ต๋๋ค |
| Linux .env ํ์ผ | ์ฌ๋ฌ ํ (๋ฐ์ดํ ์ฌ์ฉ) | ๋ฐ์ดํ๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๋ซํ ์๋์ง ํ์ธํ์ธ์ |
๋ฌธ์ ํด๊ฒฐ: FCM ๋ณ์๊ฐ ์ค์ ๋์ด ์์ง๋ง Android ํธ์๊ฐ ์๋ํ์ง ์๋ ๊ฒฝ์ฐ ์๋ฒ ๋ก๊ทธ๋ฅผ ํ์ธํ์ธ์:
[FCM] No access token availableโ ๊ฐ์ธ ํค ํ์ ์ค๋ฅ (์ค๋ฐ๊ฟ ๋ฌธ์ )[FCM] โ Push sent to user xxxโ FCM ์ ์ก ์ ์, ํด๋ผ์ด์ธํธ ์ธก ๋ฌธ์ - FCM ๋ก๊ทธ๊ฐ ์ ํ ์์ โ
FCM_PROJECT_ID๊ฐ ์ค์ ๋์ง ์์๊ฑฐ๋fcm_tokensํ ์ด๋ธ์ ํ ํฐ์ด ์์
ntfy ํธ์ (Google ์๋น์ค ์๋ ์ค๊ตญ์ฐ Android ๊ธฐ๊ธฐ)
Google Mobile Services๊ฐ ์๋ Android ๊ธฐ๊ธฐ (Huawei, Xiaomi, OPPO, vivo ๋ฑ)์ ๊ฒฝ์ฐ, PaperPhonePlus์ ntfy๋ฅผ ํตํ ํธ์ ์๋ฆผ์ ์ง์ํฉ๋๋ค.
๊ธฐ๋ณธ ์ค์ (์ค์ ๋ถํ์): ๊ณต๊ฐ ntfy.sh ์๋น์ค๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ถ๊ฐ ์ค์ ์ด ํ์ ์์ต๋๋ค.
์ ํ์ ์ค์ (์ ํ ํธ์คํ ntfy ์๋ฒ์ฉ):
NTFY_BASE_URL=https://your-ntfy-server.com
NTFY_TOKEN=your_optional_auth_token
์ฌ์ฉ์ ์ค์ ํ๋ฆ:
- ntfy ์ฑ์ ์ค์นํฉ๋๋ค (Google Play / F-Droid / ์ง์ ๋ค์ด๋ก๋)
- PaperPhonePlus ์ค์ ์ ์ด๊ณ "ntfy ํธ์" ์นด๋๋ฅผ ์ฐพ์ต๋๋ค
- ํ์๋ ํ ํฝ ์ด๋ฆ์ ๋ณต์ฌํ๊ณ ntfy ์ฑ์์ ๊ตฌ๋ ํฉ๋๋ค
- "ํธ์ ๋ฑ๋ก"์ ํญํ์ฌ ๋ฑ๋ก์ ์๋ฃํฉ๋๋ค
๋ณด์ ์ฐธ๊ณ : ntfy ์๋ฆผ์๋ ์๋ฆผ ์ ๋ชฉ๊ณผ ์์ฝ์ด ํ๋ฌธ์ผ๋ก ํฌํจ๋ฉ๋๋ค (์ค์ ๋ฉ์์ง ๋ด์ฉ์ด ์๋). ๋ ๋์ ๋ณด์์ ์ํด ntfy ์๋ฒ๋ฅผ ์ ํ ํธ์คํ ํ๋ ๊ฒ์ ๊ณ ๋ คํ์ธ์.
APNS ํธ์ (iOS ๋ค์ดํฐ๋ธ ์ฑ)
APNS (Apple Push Notification Service)๋ Capacitor๋ก ๋น๋๋ iOS ๋ค์ดํฐ๋ธ ์ฑ์ ํธ์ ์๋ฆผ์ ์ ์กํฉ๋๋ค. ๋ ๊ฐ์ง ์ค์ ์ต์ ์ด ์์ต๋๋ค:
์ต์ A: ์ง์ ์ค์ (์ฑ ๊ฐ๋ฐ์ ์๋ฒ)
- Apple Developer์ ๋ก๊ทธ์ธ โ Certificates, Identifiers & Profiles โ Keys
- + ํด๋ฆญํ์ฌ ์ Key ์์ฑ โ Apple Push Notifications service (APNs) ์ฒดํฌ โ ๋ฑ๋ก
.p8ํ์ผ ๋ค์ด๋ก๋ (โ ๏ธ ํ ๋ฒ๋ง ๋ค์ด๋ก๋ ๊ฐ๋ฅ!) ๋ฐ Key ID ๊ธฐ๋ก- Apple Developer Membership ํ์ด์ง์์ Team ID ๊ธฐ๋ก (10์ ์์ซ์)
server/.env์ ์ถ๊ฐ:
APNS_TEAM_ID=AB12CD34EF
APNS_KEY_ID=LH4Z9YN3P7
APNS_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIGTAgEA...(.p8 ํ์ผ ๋ด์ฉ)...\n-----END PRIVATE KEY-----"
APNS_BUNDLE_ID=com.yourcompany.paperphoneplus
APNS_SANDBOX=false
APNS_SANDBOX: ๊ฐ๋ฐ/TestFlight ๋น๋์ ๊ฒฝ์ฐtrue, App Store ํ๋ก๋์ ์ ๊ฒฝ์ฐfalse๋ก ์ค์ ํฉ๋๋ค.
์ต์ B: ํธ์ ๋ฆด๋ ์ด ๊ฒฝ์ (์ ํ ํธ์คํ ์๋ฒ)
๋ค๋ฅธ ์ฌ๋์ iOS ์ฑ (์: App Store์์ ๋ค์ด๋ก๋)์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ๊ฐ๋ฐ์์ Apple ์๊ฒฉ ์ฆ๋ช ์ด ์์ผ๋ฏ๋ก APNS ํธ์๋ฅผ ์ง์ ์ ์กํ ์ ์์ต๋๋ค. ๋์ ํธ์ ๋ฆด๋ ์ด๋ฅผ ์ฌ์ฉํ์ธ์.
์๋ ๋ฐฉ์:
โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโ
โ ์
ํ ํธ์คํ
์๋ฒ โ HTTP โ ์ฑ ๊ฐ๋ฐ์ ์๋ฒ โ APNS โ Apple โ
โ (Apple ์๊ฒฉ ์ฆ๋ช
์์)โโโโโโโโโ (.p8 Key + ๋ฆด๋ ์ด ๋ณด์ ) โโโโโโโโโ โโโ ๐ฑ โ
โ โ โ โ โโโโโโโโโโโ
โ APNS_RELAY_URL=... โ โ APNS_TEAM_ID=... โ
โ APNS_RELAY_KEY=... โ โ APNS_RELAY_SECRET=... โ
โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโ
1๋จ๊ณ: ์ฑ ๊ฐ๋ฐ์๊ฐ ๋ฆด๋ ์ด ์๋ํฌ์ธํธ๋ฅผ ํ์ฑํํฉ๋๋ค
์ฑ ๊ฐ๋ฐ์์ ์๋ฒ (์ด๋ฏธ APNS ์๊ฒฉ ์ฆ๋ช ์ด ์๋)์์ ๋ฆด๋ ์ด ์ํฌ๋ฆฟ์ ์ค์ ํฉ๋๋ค:
# ์ฑ ๊ฐ๋ฐ์ ์๋ฒ .env (์ด๋ฏธ APNS_TEAM_ID ๋ฑ์ด ์ค์ ๋์ด ์์)
APNS_RELAY_SECRET=a_long_random_shared_secret
์ด๋ ๊ฒ ํ๋ฉด POST /api/push-relay/apns ํธ์ ๋ฆด๋ ์ด ์๋ํฌ์ธํธ๊ฐ ์๋์ผ๋ก ํ์ฑํ๋ฉ๋๋ค.
2๋จ๊ณ: ์ ํ ํธ์คํ ์ฌ์ฉ์๊ฐ ๋ฆด๋ ์ด๋ฅผ ์ค์ ํฉ๋๋ค
์ ํ ํธ์คํ ์๋ฒ๋ ๋ ๊ฐ์ ๋ณ์๋ง ํ์ํฉ๋๋ค โ Apple ์๊ฒฉ ์ฆ๋ช ์ด ํ์ ์์ต๋๋ค:
# ์
ํ ํธ์คํ
์๋ฒ .env
APNS_RELAY_URL=https://app-developer-server.com
APNS_RELAY_KEY=the_shared_secret_from_step_1
์๋ ๋ฐฉ์:
- ์
ํ ํธ์คํ
์๋ฒ๊ฐ ์คํ๋ผ์ธ ๋ฉ์์ง๋ฅผ ์์ โ ๋ก์ปฌ
apns_tokensํ ์ด๋ธ์์ ์ฌ์ฉ์์ iOS ๊ธฐ๊ธฐ ํ ํฐ์ ์กฐํํฉ๋๋ค - ๊ธฐ๊ธฐ ํ ํฐ + ํธ์ ์ ๋ชฉ/๋ณธ๋ฌธ์ HTTP POST๋ก ๋ฆด๋ ์ด์ ์ ์กํฉ๋๋ค
- ๋ฆด๋ ์ด๊ฐ ํค๋ฅผ ๊ฒ์ฆํ ํ ์์ฒด APNS ์๊ฒฉ ์ฆ๋ช ์ ์ฌ์ฉํ์ฌ Apple์ ์ ์กํฉ๋๋ค
- ๋ฆด๋ ์ด๊ฐ ๋ง๋ฃ๋ ํ ํฐ ๋ชฉ๋ก์ ๋ฐํํฉ๋๋ค; ์ ํ ํธ์คํ ์๋ฒ๊ฐ ์๋์ผ๋ก ๋ก์ปฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ ๋ฆฌํฉ๋๋ค
์ฐ์ ์์: ๋ก์ปฌ APNS ์๊ฒฉ ์ฆ๋ช โ ํธ์ ๋ฆด๋ ์ด โ ๊ฑด๋๋ฐ๊ธฐ (๋ฌด์). ๋ ๋ค ์ค์ ๋ ๊ฒฝ์ฐ ๋ก์ปฌ ์ง์ ์ฐ๊ฒฐ์ด ์ฐ์ ํฉ๋๋ค.
๋ณด์ ์ฐธ๊ณ : ๋ฆด๋ ์ด๋ ํธ์ ์๋ฆผ ์ ๋ชฉ๊ณผ ์์ฝ๋ง ์ ์กํฉ๋๋ค (์: "๋๊ตฐ๊ฐ ๋ฉ์์ง๋ฅผ ๋ณด๋์ต๋๋ค"), ์ค์ ๋ฉ์์ง ๋ด์ฉ์ ์ ์กํ์ง ์์ต๋๋ค. ๊ธฐ๊ธฐ ํ ํฐ์ผ๋ก๋ ์ฌ์ฉ์ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ ์ ์์ต๋๋ค.
ํธ์ ๋ฆด๋ ์ด (์ ์ฒด ์ฑ๋)
๋ค๋ฅธ ์ฌ๋์ด ๊ฒ์ํ ์ฑ (์: App Store/Google Play์์)์ ์ฌ์ฉํ๋ ์ ํ ํธ์คํ ์๋ฒ ์ด์์์ ๊ฒฝ์ฐ, ๊ฐ๋ฐ์์ ํธ์ ์๊ฒฉ ์ฆ๋ช (Apple .p8 Key / Firebase ์๋น์ค ๊ณ์ / OneSignal API Key)์ด ์์ต๋๋ค.
ํธ์ ๋ฆด๋ ์ด ์์คํ ์ APNS, FCM, OneSignal ์ฑ๋์ ๋ํ ๋ฆด๋ ์ด ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค:
์ฑ ๊ฐ๋ฐ์๊ฐ ์๋ฒ์์ ๋ฆด๋ ์ด ์๋ํฌ์ธํธ๋ฅผ ํ์ฑํํฉ๋๋ค:
# ์ฑ ๊ฐ๋ฐ์ ์๋ฒ .env
APNS_RELAY_SECRET=a_long_random_string
FCM_RELAY_SECRET=a_long_random_string
ONESIGNAL_RELAY_SECRET=a_long_random_string
์ ํ ํธ์คํ ์ฌ์ฉ์๋ ๋ฆด๋ ์ด URL๊ณผ ํค๋ง ํ์ํฉ๋๋ค โ ํธ์ ์๋น์ค ์๊ฒฉ ์ฆ๋ช ์ด ํ์ ์์ต๋๋ค:
# ์
ํ ํธ์คํ
์๋ฒ .env
# APNS (iOS ๋ค์ดํฐ๋ธ ํธ์)
APNS_RELAY_URL=https://app-developer-server.com
APNS_RELAY_KEY=shared_secret
# FCM (Android ๋ค์ดํฐ๋ธ ํธ์)
FCM_RELAY_URL=https://app-developer-server.com
FCM_RELAY_KEY=shared_secret
# OneSignal (Median.co ๋ํ ์ฑ)
ONESIGNAL_RELAY_URL=https://app-developer-server.com
ONESIGNAL_RELAY_KEY=shared_secret
์ฐ์ ์์: ๋ก์ปฌ ์๊ฒฉ ์ฆ๋ช โ ํธ์ ๋ฆด๋ ์ด โ ๊ฑด๋๋ฐ๊ธฐ (๋ฌด์). ๋ ๋ค ์ค์ ๋ ๊ฒฝ์ฐ ๋ก์ปฌ ์ง์ ์ฐ๊ฒฐ์ด ์ฐ์ ํฉ๋๋ค.
๊ณต์ ํธ์ ๋ฆด๋ ์ด
์ ํ ํธ์คํ ์๋ฒ ์ด์์๋ ๊ณต์ ํธ์ ๋ฆด๋ ์ด๋ฅผ ์ฌ์ฉํ์ฌ ํธ์ ์๊ฒฉ ์ฆ๋ช ์ ์ค์ ํ์ง ์๊ณ ๋ iOS/Android ํธ์ ์๋ฆผ์ ํ์ฑํํ ์ ์์ต๋๋ค:
# 2026-05-18
APNS_RELAY_URL=https://619.chat
APNS_RELAY_KEY=EzmpqftbsENaRUO6BTABxLV96q7RuEDyokXJr1DWdDjL54cLg7yXVUQqydCQvxrX
FCM_RELAY_URL=https://619.chat
FCM_RELAY_KEY=EzmpqftbsENaRUO6BTABxLV96q7RuEDyokXJr1DWdDjL54cLg7yXVUQqydCQvxrX
ONESIGNAL_RELAY_URL=https://619.chat
ONESIGNAL_RELAY_KEY=EzmpqftbsENaRUO6BTABxLV96q7RuEDyokXJr1DWdDjL54cLg7yXVUQqydCQvxrX
์ ์ค์ ์
ํ ํธ์คํ
์๋ฒ์ .env ํ์ผ์ ์ถ๊ฐํ์ธ์.
๋ผ์ด์ ์ค
์ด ํ๋ก์ ํธ๋ GNU Affero General Public License v3.0 (AGPL-3.0) ํ์ ๋ผ์ด์ ์ค๊ฐ ๋ถ์ฌ๋ฉ๋๋ค.