kappa 4825cf4351 Align input field width with message bubbles
- Set input-wrapper to 70% width to match message bubble max-width
- Remove unnecessary max-width constraints from containers

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 10:42:41 +09:00

Chat App

Cloudflare Workers + Durable Objects + React 기반 실시간 멀티유저 채팅 애플리케이션

아키텍처

┌─────────────────┐     WebSocket     ┌──────────────────┐     ┌─────────────────┐
│   React         │ ←───────────────→ │  Cloudflare      │ ←─→ │ Durable Object  │
│   Frontend      │                   │  Worker          │     │ (ChatRoom)      │
│   (Pages)       │                   │  (라우팅)        │     │ (상태관리)      │
└─────────────────┘                   └──────────────────┘     └─────────────────┘
                                              ↓
                                      ┌──────────────────┐
                                      │  OpenAI API      │
                                      │  (AI 방장)       │
                                      └──────────────────┘

기술 스택

분류 기술
백엔드 Cloudflare Workers, Durable Objects
실시간 통신 WebSocket Hibernation API
AI OpenAI GPT-4o-mini, Function Calling
프론트엔드 React + Vite + TypeScript
검증 Zod (입력 검증)
보안 DOMPurify (XSS 방지), Rate Limiting
배포 Cloudflare Workers + Pages

프로젝트 구조

chat-app/
├── worker/                      # Cloudflare Worker 백엔드
│   ├── src/
│   │   ├── index.ts            # Worker 엔트리포인트, 라우팅, CORS
│   │   ├── ChatRoom.ts         # Durable Object 채팅방 클래스
│   │   ├── AIManager.ts        # AI 방장 (OpenAI 연동)
│   │   ├── Context7Client.ts   # 기술 문서 검색 클라이언트
│   │   ├── validation.ts       # Zod 입력 검증 스키마
│   │   └── RateLimiter.ts      # 슬라이딩 윈도우 Rate Limiter
│   ├── wrangler.toml           # Worker 설정
│   └── package.json
├── frontend/                    # React 프론트엔드
│   ├── src/
│   │   ├── App.tsx
│   │   ├── components/
│   │   │   ├── ChatRoom.tsx    # 채팅방 UI
│   │   │   ├── MessageList.tsx # 메시지 목록
│   │   │   └── MessageInput.tsx# 입력창
│   │   ├── hooks/
│   │   │   └── useWebSocket.ts # WebSocket 연결 훅
│   │   └── utils/
│   │       └── sanitize.ts     # XSS 방지 유틸리티
│   ├── vite.config.ts
│   └── package.json
└── package.json                 # 모노레포 루트

기능

채팅 기능

  • 실시간 메시지 송수신
  • 사용자 입장/퇴장 알림
  • 접속자 목록 표시
  • 사용자 이름 변경

AI 방장 (@방장)

  • IT 기술 관련 질문 응답
  • 자동 환영 메시지
  • 채팅 중재 (부적절한 언어 경고)
  • 명령어 처리 (/help, /rules, /pricing 등)
  • Context7 API 연동 기술 문서 검색

보안 기능

  • CORS 제한 (특정 도메인만 허용)
  • 입력값 검증 (Zod 스키마)
  • XSS 방지 (DOMPurify + 안전한 마크다운)
  • Rate Limiting (사용자별 요청 제한)
  • API 타임아웃 (15초)
  • 보안 헤더 (CSP, X-Frame-Options 등)

API 엔드포인트

WebSocket

GET /api/rooms/:roomId/websocket?name=사용자이름

WebSocket 연결을 위한 엔드포인트

REST

GET /api/rooms/:roomId/users   # 채팅방 접속자 목록
GET /api/health                 # 헬스체크

WebSocket 메시지 형식

클라이언트 → 서버

// 메시지 전송
{ type: 'message', content: '안녕하세요' }

// 이름 변경
{ type: 'rename', name: '새이름' }

서버 → 클라이언트

// 일반 메시지
{ type: 'message', id: 'uuid', name: '사용자', content: '내용', timestamp: 1234567890, isBot?: boolean }

// 입장
{ type: 'join', id: 'uuid', name: '사용자', timestamp: 1234567890 }

// 퇴장
{ type: 'leave', id: 'uuid', name: '사용자', timestamp: 1234567890 }

// 사용자 목록
{ type: 'userList', users: ['사용자1', '사용자2'], timestamp: 1234567890 }

// 에러
{ type: 'error', message: '에러 메시지', timestamp: 1234567890 }

로컬 개발

요구사항

  • Node.js 18+
  • pnpm

설치 및 실행

# 의존성 설치
pnpm install

# Worker 로컬 실행
cd worker
pnpm dev

# Frontend 로컬 실행 (새 터미널)
cd frontend
pnpm dev

환경변수

Worker:

  • OPENAI_API_KEY: OpenAI API 키 (wrangler.toml 또는 secrets)

배포

Worker 배포

cd worker
pnpm deploy

Frontend 배포

cd frontend
pnpm build
pnpm pages:deploy

배포 URL

서비스 URL
Worker API https://chat-worker.kappa-d8e.workers.dev
Frontend https://chat-frontend-4wf.pages.dev
Custom Domain https://chat.anvil.it.com

보안 구현 상세

CORS 설정

const ALLOWED_ORIGINS = [
  'https://chat.anvil.it.com',
  'https://chat-frontend-4wf.pages.dev',
  'http://localhost:5173',
  'http://localhost:3000',
];
  • 정확한 도메인 매칭만 허용
  • Pages 프리뷰 도메인 패턴 제한 ([commit].chat-frontend-4wf.pages.dev)

입력 검증

// 메시지: 1-2000자
const MessageSchema = z.object({
  type: z.literal('message'),
  content: z.string().min(1).max(2000).trim(),
});

// Room ID: 영문숫자, 하이픈, 언더스코어만
const RoomIdSchema = z.string()
  .min(1).max(100)
  .regex(/^[a-zA-Z0-9_-]+$/);

// 사용자 이름: 1-50자
const UserNameSchema = z.string().min(1).max(50).trim();

Rate Limiting

대상 제한 차단 시간
AI 요청 15회/분 2분
중재 위반 3회/5분 30분

보안 헤더

X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: default-src 'self'; connect-src 'self' wss: https:; ...

AI 방장 명령어

명령어 설명
/help 도움말 보기
/rules 채팅방 규칙
/users 현재 접속자 목록
/pricing Anvil Hosting 요금 안내
/specs 서버 스펙 안내
/regions 리전 정보
/contact 텔레그램 상담 연결
@방장 [질문] AI에게 질문하기

AI Function Calling

AI 방장은 다음 기능을 호출할 수 있습니다:

함수 설명
searchDocumentation 기술 문서 검색 (Context7 API)
getAnvilHostingInfo Anvil Hosting 정보 조회 (pricing, specs, regions, features, contact)
getCurrentDateTime 현재 날짜/시간 조회 (한국 기준)
getWeather 도시별 날씨 조회 (Open-Meteo API)

텔레그램 봇 연동

Anvil Hosting 문의 시 자동으로 텔레그램 봇 연결 안내:

날씨 지원 도시

서울, 부산, 도쿄, 오사카, 뉴욕, 런던

사용 예시

@방장 오늘 날짜가 뭐야?
@방장 서울 날씨 어때?
@방장 도쿄 날씨 알려줘

라이선스

Private - Anvil Hosting

Description
Anvil Lounge - Real-time chat with AI moderation
Readme MIT 146 KiB
Languages
TypeScript 93.4%
CSS 6.3%
HTML 0.3%