본문으로 건너뛰기

아키텍처

전체 흐름

기본값(로컬 tmux + 터널):

코드
Browser (phone)
  |
  | HTTPS
  v
Cloudflare Quick Tunnel
  |
  | HTTP/WS
  v
Local Termbridge server
  |
  | PTY
  v
Tmux session

직접 샌드박스 모드(Daytona, 터널 없음):

코드
Browser (phone)
  |
  | HTTPS
  v
Daytona preview link
  |
  | HTTP/WS
  v
Termbridge server (sandbox)
  |
  | PTY
  v
Tmux session (sandbox)

--no-tunnel이 설정되면 Termbridge은 Cloudflare을 건너뛰고 제공된 공개 URL를 사용합니다.

컴포넌트

CLI 프로세스

  • UI 에셋을 제공하는 HTTP 서버를 시작합니다.
  • 터미널 I/O용 WebSocket 서버를 시작합니다.
  • 하나 이상의 tmux 세션(또는 Daytona PTY)을 생성합니다.
  • 일회용 교환 토큰을 발급합니다.
  • 터널이 활성화된 경우 터널을 시작하고 모니터링합니다(현재 Cloudflare).
  • 직접 샌드박스 모드에서는 샌드박스 안에서 Termbridge을 실행하고 공유 URL을 기다립니다.

터미널 백엔드

  • tmux 백엔드: 실제 키 입력과 출력을 위해 node-pty을 사용해 tmux에 연결합니다.
  • Daytona 백엔드: 샌드박스 안에서 Daytona PTY를 사용합니다.
  • 전체 색상 지원을 위해 TERM=xterm-256color 및 COLORTERM=truecolor을 설정합니다.
  • tmux의 경우 UI을 깔끔하게 유지하도록 tmux 상태 표시줄을 비활성화합니다.

터미널 레지스트리

  • 사용 가능한 터미널 세션을 추적합니다.
  • UI에 목록을 제공하고 선택한 터미널 ID를 확인합니다.

인증 및 세션

  • 일회용 토큰은 URL으로 출력되고 QR 코드에 인코딩됩니다.
  • GET /__tb/s/:token가 토큰을 교환하고 쿠키 세션을 설정합니다.
  • WebSocket 연결에는 유효한 세션 쿠키와 CSRF 토큰이 필요합니다.
  • CSRF 토큰은 GET /__tb/api/csrf을 통해 가져와 WebSocket 쿼리 매개변수로 전달됩니다.

프록시 모드

--proxy <port>이 전달되면:

  • 모든 termbridge 경로는 /__tb/ 접두사 아래에 있습니다.
  • /__tb/가 아닌 요청은 대상 앱으로 프록시됩니다.
  • UI는 뷰 전환기를 통해 터미널/미리보기 뷰를 표시합니다.
  • 미리보기 뷰는 /를 가리키는 iframe을 불러옵니다.
  • HMR용 WebSocket 연결은 대상에 전달됩니다.

보안

  • CSRF 보호: 사이트 간 탈취를 방지하기 위해 WebSocket 업그레이드에 CSRF 토큰이 필요합니다.
  • 입력 제한: 64KB HTTP 본문, WebSocket 메시지 1MB, 메시지당 터미널 입력 64KB입니다.
  • 요청 제한: 토큰 교환과 WebSocket 연결에 IP별 제한을 적용합니다.

API 엔드포인트

프록시된 앱과의 충돌을 피하기 위해 모든 경로는 /__tb/ 접두사 아래에 있습니다:

  • GET /__tb/healthz - 상태 확인
  • GET /__tb/s/:token - 토큰 교환 → 쿠키 → 리디렉션
  • GET /__tb/api/terminals - 터미널 목록
  • GET /__tb/api/csrf - WebSocket용 CSRF 토큰 가져오기
  • GET /__tb/api/config - 설정 가져오기(proxyPort, devProxyUrl)
  • POST /__tb/api/terminals - 새 tmux 세션 생성
  • GET /__tb/app/* - SPA 대체 경로
  • WS /__tb/ws/terminal/:terminalId?csrf=<token> - 터미널 I/O 스트리밍

프록시 모드가 활성화된 경우:

  • GET / - 프록시된 앱으로 리디렉션(비활성화된 경우 /__tb/app)
  • * /* - 대상 앱으로 프록시됨

WebSocket 프로토콜

클라이언트 -> 서버:

JSON
{ "type": "input", "data": "ls -la" }
{ "type": "resize", "cols": 120, "rows": 32 }
{ "type": "control", "key": "ctrl_c" }

서버 -> 클라이언트:

JSON
{ "type": "output", "data": "..." }
{ "type": "status", "state": "connected" }

UI 스택

  • Vite + TanStack Router
  • xterm.js + Fit 애드온 + WebGL 렌더러
  • 입력 바를 고정한 모바일 우선 레이아웃
  • 지수형 백오프로 자동 재연결(최대 10회 재시도)
  • 연결 상태 표시기