Initial hop. implementation and UGREEN NAS deployment guide
This commit is contained in:
519
SPEC.md
Normal file
519
SPEC.md
Normal file
@@ -0,0 +1,519 @@
|
||||
# hop. 프로젝트 개발 명세서
|
||||
|
||||
> 2026-10-08 사용자 수정안: 신규 전송은 파일·HTTP/HTTPS URL만 지원하고 일반 텍스트 전송과 유형 탭을 제거한다. 기기 선택 시 클립보드 URL 읽기를 시도하는 통합 전송 UX는 [TRANSFER_UX.md](docs/TRANSFER_UX.md)를 우선 적용한다. 이메일 발송은 SMTP 대신 Resend(`hello@noname.company`)를 사용한다. 기본 테마는 검정이다.
|
||||
|
||||
> 2026-10-12 추가 수정안: 명시적으로 수락한 전송은 링크 자동 새 탭 열기·파일 자동 다운로드로 완료하고 별도 도착 확인 팝업을 제거한다. 비로그인 전송 내역 UI를 제거하며 취소는 전송 버튼 자리에서 처리한다. 수락 요청은 중앙 고대비 팝업으로 표시한다. 프런트엔드는 Vue + JavaScript로 전환한다. 개발·테스트 환경은 1자 이상 비밀번호를 허용하고 운영은 12자 이상 규칙을 유지한다.
|
||||
|
||||
> 2026-10-12 최종 화면·보관 구조 수정: [TEMPORARY_VAULT.md](docs/TEMPORARY_VAULT.md)를 우선 적용한다. 메인은 중앙 기기 선택과 선택 후 전송 모달/하단 시트로 구성하며, 보관함 안에 임시 저장·날짜별 받은 항목을 둔다. 로그인 중 수신만 계정에 기록한다. 모든 저장은 최대 7일이고 새 이메일 원격 전송·영구 즐겨찾기·익명 사후 가져오기를 제거한다. 한 기기는 한 방에만 속하며 방 참여자는 주변 목록과 격리한다. 방장이 나가면 모두 나간다.
|
||||
|
||||
> 2026-10-12 최종 레이아웃·콘텐츠 선택 수정: QR은 기기 카드 영역에 함께 배치하고 내 기기는 푸터의 작은 상태/설정 버튼으로 옮긴다. 전송 모달은 링크·파일·로그인 보관함 중 현재 콘텐츠 하나와 전송 버튼 하나만 표시하며 선택 방법에 따라 크기를 변경하지 않는다. 보관함 항목 선택만으로 전송하지 않는다.
|
||||
|
||||
버전: 3.0
|
||||
서비스명: hop.
|
||||
슬로건: 기기 사이를 가볍게.
|
||||
서비스 주소: https://hop.noname.company
|
||||
배포 환경: UGREEN DXP6800PRO / Docker Compose
|
||||
|
||||
이 문서는 이전 명세를 대체하는 독립적인 개발 기준이다. 이전 버전의 계정 중심 홈이나 기기별 영구 수신함을 함께 구현하지 않는다.
|
||||
|
||||
## 1. 목적과 제품 방향
|
||||
|
||||
hop.은 가입 없이 접속한 기기 사이에서 파일, 텍스트, URL을 간단히 전달하는 웹서비스다. 홈 화면은 현재 연결 가능한 기기를 보여주고, 기기를 선택해 전송 요청을 보내며, 상대가 수락하면 전달한다.
|
||||
|
||||
멀리 있거나 사이트에 접속하지 않은 사람에게는 이메일 주소로 전달한다. 이 보조 기능은 이메일 인증 계정을 사용하고 서버에 저장해 나중에 확인할 수 있게 한다. 자기 이메일로 저장하면 다른 기기에서 꺼내 쓸 수 있다.
|
||||
|
||||
최우선 목표는 누구나 가볍게 사용할 수 있는 것이다. 핵심은 ‘접속 → 대상 선택 → 수락 → 전달’이다. 복잡한 폴더 관리나 클라우드 드라이브 기능을 중심에 두지 않는다. 기기 이름 변경을 지원하며 계정 가입을 홈 진입 조건으로 만들지 않는다.
|
||||
|
||||
## 2. 두 가지 전송 방식
|
||||
|
||||
| 항목 | 즉석 전송 | 이메일 전송 |
|
||||
|---|---|---|
|
||||
| 목적 | 현재 접속한 기기로 바로 전달 | 원격 또는 오프라인 회원에게 전달 |
|
||||
| 회원가입 | 불필요 | 송신자와 수신자 모두 인증 계정 필요 |
|
||||
| 대상 | 같은 네트워크의 접속 기기 또는 QR·코드로 연결한 기기 | 인증된 이메일 주소 |
|
||||
| 수락 | 매 전송 수락 | 낯선 발신자는 요청 수락, 허용 발신자는 자동 수신 |
|
||||
| 수신 범위 | 선택한 브라우저 기기 | 계정의 모든 로그인 기기에서 공유 |
|
||||
| 오프라인 대상 | 전송 불가 | 서버에 저장 가능 |
|
||||
| 보관 | 익명 브라우저에서 유효기간 동안 접근 | 계정 보관함에서 접근 |
|
||||
|
||||
초기 파일 전송은 NAS 서버를 경유한다. 즉석 전송은 즉시 전달하는 UX를 뜻하며 P2P나 서버 미저장을 뜻하지 않는다. WebRTC P2P는 초기 범위에서 제외한다.
|
||||
|
||||
## 3. 접속 기기와 이름
|
||||
|
||||
### 3.1 익명 기기 세션
|
||||
|
||||
접속 시 서버가 임의 기기 식별자와 익명 세션을 발급한다. 브라우저 지문은 수집하지 않는다. 공개 기기 식별자는 인증 토큰과 분리한다. 인증 비밀값은 HttpOnly 쿠키로 관리하고 클라이언트가 제출한 기기 ID만으로 권한을 인정하지 않는다.
|
||||
|
||||
브라우저 또는 프로필을 하나의 기기로 취급한다. 동일 브라우저의 여러 탭은 하나로 합치고 모든 연결이 종료된 뒤 유예 시간 후 오프라인 처리한다. 다른 브라우저나 프로필은 별도 기기다. 초기 익명 세션의 서버 유효기간은 30일이며 쿠키 삭제·만료 시 이전 익명 수신함을 복구할 수 없음을 안내한다.
|
||||
|
||||
### 3.2 기기 이름 변경
|
||||
|
||||
첫 방문에는 플랫폼 기반 기본 이름을 제안하고 언제든 수정할 수 있다. 예: Mac Studio, Galaxy S25 Ultra, iPad Pro, Nothing Phone. 이름은 1~40자이며 제어문자와 HTML 실행을 허용하지 않는다. 변경은 재접속 후 유지하고 연결된 상대의 목록에도 반영한다.
|
||||
|
||||
이름은 중복 가능하므로 서버가 생성한 짧은 구분 코드를 함께 표시한다. 사용자가 이름을 ‘관리자’나 특정인으로 지정해도 신원이 인증됐다고 표현하지 않는다. 이메일 계정과 기기 이름은 별개다. 로그인·로그아웃으로 기기 이름은 바뀌지 않는다. 익명 표시에는 이메일을 노출하지 않는다.
|
||||
|
||||
### 3.3 발견 범위
|
||||
|
||||
전체 사이트 접속자를 보여주지 않는다. 다음 범위의 기기만 목록에 표시한다.
|
||||
|
||||
1. 같은 네트워크: 서버가 검증한 클라이언트 공인 IP가 정확히 같은 온라인 기기 후보.
|
||||
2. 연결 공간: QR 또는 짧은 코드로 같은 임시 공간에 참여한 온라인 기기.
|
||||
|
||||
같은 공인 IP는 물리적 근접이나 동일 Wi-Fi의 확실한 증거가 아니다. 회사 공유망이나 통신사 공유 IP에서 예상보다 넓은 범위가 표시될 수 있다. UI는 ‘주변’이라는 상위 개념 아래 해당 목록을 ‘같은 네트워크’로 명시하고 실제 거리나 Bluetooth 탐색이라고 표시하지 않는다. IPv6 주소의 임의 /64 통합은 하지 않는다. 같은 네트워크 탐색은 운영자가 끌 수 있으며 이때 QR·코드는 계속 제공한다.
|
||||
|
||||
기본 수신 대기는 켜져 있으며 기기 이름 옆에 ‘발견 허용’ 스위치를 제공한다. 끄면 자동 목록에서 숨고 새 즉석 요청도 받지 않는다. 기존 수락 전송은 계속 진행할 수 있다. 목록 노출 자체가 민감한 환경을 위해 설정 상태를 눈에 띄게 표시한다.
|
||||
|
||||
IP는 브라우저에 공개하지 않는다. 그룹 키는 서버 비밀값으로 HMAC 처리하고 접속 중에만 사용한다. 이름 조회·기기 목록 이벤트는 서버가 발견 범위를 검증한 세션에만 보낸다. 대상 ID를 알아도 다른 범위 기기로 직접 요청할 수 없다.
|
||||
|
||||
### 3.4 QR와 코드 연결
|
||||
|
||||
사용자가 임시 연결 공간을 만들면 QR와 사람이 입력할 짧은 코드를 제공한다. QR는 충분한 엔트로피의 초대 토큰을 사용하고 코드는 예측·대입 방지를 위해 발급·조회 횟수를 제한한다. 기본 공간 유효기간은 10분, 최대 참여 기기는 10대다.
|
||||
|
||||
다른 네트워크의 기기도 참여할 수 있다. 기본은 QR·코드를 가진 기기가 즉시 참여하며 생성자에게 참여 사실과 이름을 알린다. 생성자가 ‘참여 승인 필요’를 켠 공간에서는 이름을 확인한 뒤 승인한다. 코드 자체를 참여 허가로 취급하지만 파일 전송 수락은 생략하지 않는다. 공간에 들어온 이후에도 파일 전송은 별도 수락을 받는다. 생성자는 참여자를 내보내거나 공간을 닫을 수 있다. 만료·퇴장 이후 새 요청은 금지하지만 이미 수락한 전송과 수신 자료의 접근권은 자체 유효기간에 따라 유지한다. QR·코드는 다운로드 링크가 아니다.
|
||||
|
||||
## 4. 즉석 전송 흐름
|
||||
|
||||
1. 접속 후 자신의 기기 이름과 발견 허용 상태를 확인한다.
|
||||
2. 같은 네트워크 또는 연결 공간에서 온라인 대상 기기를 선택한다.
|
||||
3. 텍스트·URL을 입력하거나 파일을 선택한다.
|
||||
4. 서버에 전송 요청의 메타데이터를 보낸다. 수락 전 파일 바이트를 업로드하지 않는다.
|
||||
5. 수신 기기에 발신 기기 이름·구분 코드·유형·파일명·크기·개수와 수락·거절 버튼을 표시한다.
|
||||
6. 수락 후 서버가 업로드 권한과 용량을 예약하고 송신자가 업로드한다. 텍스트·URL도 수락 후 전달한다.
|
||||
7. 저장이 확정되면 수신 기기에 복사·링크 열기·다운로드 동작을 제공한다.
|
||||
8. 완료 후 양쪽의 이번 기기 전송 목록에 결과를 표시한다.
|
||||
|
||||
요청 기본 유효기간은 60초다. 거절·만료·오프라인 대상에는 업로드를 시작하지 않는다. 수락 직전 대상 온라인·수신 상태를 다시 검사한다. 수락 후 잠시 연결이 끊겨도 완료된 자료는 유효한 익명 세션으로 다시 접속해 확인할 수 있다. 업로드는 초기 버전에서 중단 지점 재개를 보장하지 않으며 재시도 시 새 업로드로 처리한다.
|
||||
|
||||
자동 다운로드, 링크 자동 열기, 전체 화면 요청 팝업은 사용하지 않는다. 수락 전 텍스트 본문이나 이미지 미리보기를 자동 노출하지 않는다. 요청 내용을 악성 HTML로 실행하지 않는다. 익명 기기 이름은 신뢰할 수 있는 신원 증명이 아니므로 수신 카드에 필요한 구분 정보를 제공한다.
|
||||
|
||||
## 5. 이메일 계정과 인증
|
||||
|
||||
### 5.1 회원가입과 로그인
|
||||
|
||||
이메일이 로그인 ID이자 원격 전송 주소다. 별도 username이나 @아이디는 만들지 않는다. 가입 필드는 이메일, 비밀번호, 비밀번호 확인, 표시 이름이다. 비밀번호는 최소 12자, 긴 비밀번호와 붙여넣기를 허용하고 Argon2id로 해싱한다.
|
||||
|
||||
가입 후 이메일 인증을 필수로 한다. 인증 전 원격 전송·요청 수락·계정 파일 업로드를 금지한다. 인증 메일 재발급, 비밀번호 찾기·재설정, 비밀번호·이메일 변경, 로그아웃, 다른 세션 전체 로그아웃을 지원한다. 이메일 변경은 새 주소 인증 후 확정하고 이전 주소로 안내한다.
|
||||
|
||||
메일은 SMTP로 발송하고 인증·복구 토큰은 일회용, 유효기간 제한, 해시 저장을 적용한다. 가입·복구 응답은 계정 존재 여부를 불필요하게 노출하지 않는다. 이메일 정규화는 도메인 소문자화 등 명시된 보수적 규칙을 사용하며 제공자별 점·별칭 제거를 임의 적용하지 않는다.
|
||||
|
||||
세션은 서버 기반이며 쿠키는 HttpOnly, Secure, SameSite=Lax를 적용한다. 인증 성공 시 세션을 회전하여 고정 공격을 막는다. 비밀번호 재설정·정지·탈퇴 시 기존 세션과 Socket.IO 권한을 무효화한다. 익명 세션과 계정 세션은 분리한다.
|
||||
|
||||
### 5.2 익명 수신과 로그인
|
||||
|
||||
익명으로 받은 자료를 로그인 시 자동으로 계정에 합치지 않는다. 사용자에게 ‘내 계정에 저장’ 동작을 제공하고 명시적으로 선택한 자료만 해당 계정 접근권과 용량으로 옮긴다. 원래 만료 시각은 유지한다.
|
||||
|
||||
같은 브라우저에서 계정을 바꾸거나 로그아웃해도 익명 기기 수신 자료가 남을 수 있다는 점을 명확히 안내한다. 공유 기기에서는 ‘이 기기의 익명 자료와 세션 지우기’를 제공한다. 이 동작은 이전 익명 접근권을 폐기하고 새 익명 세션을 만들며 계정 자료는 삭제하지 않는다.
|
||||
|
||||
### 5.3 운영 모드
|
||||
|
||||
관리자는 공개 회원가입을 열거나 닫을 수 있다. SMTP 설정 없이 공개 가입을 켤 수 없다. 최초 관리자 생성은 안전한 초기화 절차로만 허용한다. 계정 역할은 user/admin, 상태는 인증 대기/활성/정지/탈퇴다. 관리자 화면은 사용자 본문과 파일을 기본 열람 기능으로 제공하지 않는다. 서버 운영자의 기술적 접근 가능성이 있으므로 E2EE를 제공한다고 안내하지 않는다.
|
||||
|
||||
## 6. 이메일 원격 전송
|
||||
|
||||
### 6.1 대상 지정
|
||||
|
||||
송신자는 인증된 계정으로 로그인한 뒤 수신자의 이메일 전체를 입력한다. 이메일 일부 자동완성, 전체 회원 목록, 이메일 검색 API는 제공하지 않는다. 이메일은 대상 지정에만 쓰며 다른 사용자에게 계정의 접속 기기나 온라인 상태를 공개하지 않는다.
|
||||
|
||||
대상은 인증된 기존 회원이어야 한다. 비회원 이메일에 첨부파일, 초대 메일, 가입 유도 메일을 자동 보내지 않는다. 유효하지 않은 대상과 수신 거부 대상의 오류는 ‘이 주소로 전송할 수 없습니다’처럼 처리하여 정지·차단 사유를 공개하지 않는다. 정확한 주소 전송의 성공·실패는 계정 존재를 일부 드러낼 수 있으므로 횟수 제한을 적용한다.
|
||||
|
||||
초기에는 한 번에 이메일 한 명에게 보내며 다중 이메일 전송은 후속 범위다. 자신의 인증 이메일로 보내면 수신 요청 없이 ‘내 보관함’에 저장한다. 모든 로그인 기기는 계정 보관함과 읽음 상태를 공유한다.
|
||||
|
||||
### 6.2 낯선 발신자와 허용 목록
|
||||
|
||||
사용자는 수신 정책을 다음 중 선택한다: 처음 보는 사람은 요청으로 받기(기본), 허용한 사람에게만 받기, 다른 사람에게 받지 않기. 차단은 허용보다 우선한다.
|
||||
|
||||
처음 보는 발신자는 메타데이터 기반 수신 요청만 생성한다. 수신자는 인증된 발신 이메일·표시 이름, 유형·파일명·크기·개수를 보고 수락·거절·차단한다. ‘이 발신자는 다음부터 바로 받기’를 선택하면 허용 목록에 추가한다. 이름과 이메일 주소만 인증됐으며 실제 인물을 보증한다고 표시하지 않는다.
|
||||
|
||||
낯선 발신자의 수락 전에는 파일을 업로드하지 않고 본문·이미지를 수신자에게 자동 노출하지 않는다. 요청은 기본 7일 후 만료한다. 승인 뒤 송신자가 오프라인이라면 ‘수락됨 · 전송 대기’로 표시하고 다음 접속에서 전송을 완료하도록 한다. 송신자 파일은 서버에 없으므로 자동 전달할 수 없으며 재선택이 필요할 수 있음을 명시한다. 입력 초안의 영구 로컬 저장은 기본 제공하지 않는다.
|
||||
|
||||
허용된 발신자는 수신자가 오프라인이어도 즉시 업로드·저장할 수 있다. 따라서 ‘오프라인 원격 전송’은 허용 관계 또는 이미 수락된 요청에서 완성된다. 첫 전송을 받기 위해 한 번 수락하는 절차가 필요하다는 점을 보내기 화면에서 안내한다.
|
||||
|
||||
승인 후 미완료 전송은 24시간 내 시작해야 한다. 승인·허용 상태는 업로드 시작과 전송 확정 시 다시 검사한다. 거절·차단·정지·만료되면 예약 용량과 임시 파일을 정리한다. 계정 보관 기간은 요청 시점이 아니라 데이터 저장 확정 시점부터 계산한다.
|
||||
|
||||
## 7. 데이터 유형과 전송 처리
|
||||
|
||||
### 7.1 텍스트
|
||||
|
||||
다국어 UTF-8, 여러 줄, 기본 최대 1MiB를 지원한다. 일반 텍스트로 렌더링하고 복사 성공·실패를 안내한다. 서버 제한을 환경변수로 조정할 수 있다.
|
||||
|
||||
### 7.2 URL
|
||||
|
||||
HTTP/HTTPS만 허용한다. 여러 줄에서 URL을 분리하고 유효하지 않은 입력을 표시한다. 복사, 개별 새 탭 열기, 선택 URL 일괄 열기를 제공한다. 팝업 차단 시 개별 링크를 제시한다. 여러 URL은 전송 묶음에 속한 개별 항목으로 저장한다.
|
||||
|
||||
미리보기는 전달 완료 이후 비동기적으로 수집하고 실패해도 전송은 유지한다. 서버 요청은 DNS IPv4/IPv6, 실제 연결 IP, 각 리디렉션을 검증하여 루프백·사설망·링크 로컬·메타데이터·내부 서비스 접근을 막는다. 타임아웃·응답 크기·리디렉션을 제한한다. 임의 쿠키·인증 정보를 보내지 않는다. 내부 URL 자체 전달은 허용하고 미리보기는 생략한다. 파비콘은 제한된 이미지로 서버에서 수집하여 수신자 브라우저가 자동으로 외부 사이트를 호출하지 않게 한다.
|
||||
|
||||
### 7.3 파일
|
||||
|
||||
파일 선택, 여러 파일, 드래그 앤 드롭, 파일명·크기, 개별 업로드 진행률, 취소, 다운로드를 지원한다. 파일당 기본 2GiB이며 multipart 스트리밍으로 저장한다. 전체 파일을 메모리에 적재하지 않는다. 서버는 실제 바이트 수와 선언 크기를 검증한다.
|
||||
|
||||
묶음 내 파일마다 성공·실패를 표시하고 성공한 파일은 유지하며 실패 파일만 재시도할 수 있다. 수락은 서버에 저장된 변경 불가능한 파일 목록과 크기에 결합한다. 파일 변경·추가·크기 증가 시 새 요청이 필요하다. 요청에 없는 파일 업로드는 거절한다.
|
||||
|
||||
지원 래스터 이미지에 인증된 미리보기를 제공한다. HTML·SVG·실행 콘텐츠는 인라인 실행을 금지한다. 다운로드는 실제 접근권을 검사하고 안전한 Content-Disposition·nosniff를 적용한다. 원본 이름은 경로로 사용하지 않고 서버 생성 키로 저장하며 저장 경로를 공개하지 않는다.
|
||||
|
||||
업로드 중단·브라우저 종료·저장 공간 부족 시 임시 파일과 용량 예약을 회수한다. 미확정 업로드는 기본 24시간 내 정리한다. 초기에는 청크 재개와 백그라운드 업로드를 보장하지 않는다. 파일을 안전하다고 보증하지 않는다.
|
||||
|
||||
### 7.4 클립보드와 중복 방지
|
||||
|
||||
사용자 동작으로 붙여넣기·복사를 제공하고 권한이 있으면 클립보드 읽기 버튼을 제공한다. 자동 백그라운드 감시는 없다. 전송 요청·수락·업로드 확정은 idempotency key로 중복 처리를 막는다. 같은 키에 다른 payload가 오면 충돌로 거절한다.
|
||||
|
||||
## 8. 보관함과 상태
|
||||
|
||||
| 화면 | 대상과 기능 |
|
||||
|---|---|
|
||||
| 이번 기기 | 익명 즉석 수신·전송 결과, 복사·열기·다운로드·삭제 |
|
||||
| 받은 항목 | 계정의 전달 완료 자료, 읽음·검색·필터·즐겨찾기·삭제 |
|
||||
| 수신 요청 | 아직 수락하지 않은 원격 요청, 수락·거절·차단 |
|
||||
| 내 보관함 | 자신에게 저장한 자료, 다른 기기에서 사용·재전송 |
|
||||
| 보낸 항목 | 원격 전송의 요청·전송·확인 상태, 내 목록 삭제 |
|
||||
| 즐겨찾기 | 계정이 보존하도록 지정한 자료 |
|
||||
|
||||
영구 기기별 계정 수신함은 없다. ‘이번 기기’는 익명 세션 접근권이며 계정 받은 항목과 별개다. 일반 방문자가 다른 익명 기기의 과거 수신 내역을 볼 수 없다. 계정 목록은 검색, 유형·미확인 필터, 최신순, 페이지네이션과 다중 선택을 지원한다.
|
||||
|
||||
요청 상태는 대기/수락/거절/만료/취소, 전송 상태는 업로드 대기/업로드 중/저장 완료/실패, 읽음 상태는 미확인/확인 완료로 분리한다. 읽음은 상세 열기·명시적 복사·링크 열기·다운로드 시작에서 처리한다. 다운로드 시작은 다운로드 완료나 파일 실행을 의미하지 않는다. 목록 노출·썸네일·실시간 이벤트만으로 읽음 처리하지 않는다. 다시 미확인으로 바꿀 수 있다.
|
||||
|
||||
계정 읽음은 모든 로그인 기기에 공유되고 익명 읽음은 해당 익명 세션에만 적용한다. 확인한 자료는 자동 삭제하지 않는다. 송신자에게 확인 여부는 표시하되 수신자의 삭제·즐겨찾기 상태는 노출하지 않는다.
|
||||
|
||||
## 9. 밀어서 삭제와 실행 취소
|
||||
|
||||
삭제의 기본 의미는 ‘내 목록에서 삭제’다. 송신자가 보낸 항목을 삭제해도 수신 자료를 회수하지 않는다. 수신자가 삭제해도 송신자의 접근권은 남는다. 아직 수락되지 않은 자신이 보낸 요청은 별도 ‘요청 취소’ 동작을 제공한다.
|
||||
|
||||
- 모바일 행을 왼쪽으로 밀면 삭제 버튼을 드러내고 버튼을 눌러 삭제한다. 밀기만으로 즉시 삭제하지 않는다.
|
||||
- 한 행만 펼치고 다른 행·바깥 터치로 닫는다. 세로 스크롤과 가로 의도를 구분하고 시스템 뒤로 가기 제스처와 충돌하지 않게 한다.
|
||||
- 링크·복사·다운로드 버튼이나 텍스트 선택 조작에서 제스처를 시작하지 않는다.
|
||||
- 행을 부드럽게 접고 ‘삭제됨 · 실행 취소’를 5초 제공한다. 다중 삭제도 작업별 서버 복원 ID를 사용한다.
|
||||
- 서버의 5초 유예 안에서만 복원하고 자연 만료된 자료는 복원하지 않는다. 유예 중에는 물리 파일 정리를 보류한다.
|
||||
- 서버 실패 시 행·선택·카운트를 복원하고 이해하기 쉬운 오류를 표시한다. 다른 로그인 기기의 상태도 동기화한다.
|
||||
- 데스크톱 행 메뉴, 모바일 명시적 삭제 버튼, 키보드·스크린리더 접근을 함께 제공한다. 터치 목표 최소 44px와 동작 줄이기를 지원한다.
|
||||
- 미확인/확인 필터와 선택 삭제를 제공해 확인한 항목을 쉽게 정리한다.
|
||||
|
||||
수신 요청을 목록에서 정리할 때는 거절로 처리하여 송신자와 상태가 일치하게 한다. 이미 진행 중인 전송을 단순 목록 삭제로 숨겨서 계속 업로드하게 하지 않는다. 진행 중 전송은 먼저 취소하고 양쪽에 상태를 알린다. 즐겨찾기 직접 삭제는 해당 보존 권한도 제거한다.
|
||||
|
||||
## 10. 보관 기간과 정리
|
||||
|
||||
| 유형 | 기본 기간 |
|
||||
|---|---|
|
||||
| 텍스트 | 30일 |
|
||||
| URL | 30일 |
|
||||
| 파일 | 7일 |
|
||||
| 계정 즐겨찾기 | 자동 만료 없음 |
|
||||
| 원격 수신 요청 | 7일 |
|
||||
| 즉석 수신 요청 | 60초 |
|
||||
|
||||
일반 보관 기간은 데이터 저장 확정부터 계산하며 읽음으로 연장하지 않는다. 익명 자료에도 동일한 데이터 보관 기간을 적용하지만 익명 세션 만료·초기화 후 접근은 복구할 수 없다. 익명 즐겨찾기는 제공하지 않으며 로그인 후 내 계정에 저장해서 사용할 수 있다.
|
||||
|
||||
즐겨찾기는 사용자별 보존 권한이다. 한 사용자가 즐겨찾기해도 상대방의 기한은 늘어나지 않는다. 해제 시 원래 만료가 지났으면 즉시 만료 안내를 한다. 데이터는 유효한 접근권·즐겨찾기 또는 실행 취소 유예가 남아 있으면 유지하고 마지막 권한 종료 후 본문·파일을 정리한다. 여러 전송이 같은 파일을 참조하면 모든 참조를 확인한다.
|
||||
|
||||
운영자가 기본 기간을 바꾸면 새 자료에 적용한다. 파일·메타데이터·용량 회계를 함께 정리하며 고아 파일과 DB 불일치를 정기 확인한다. 최소 감사 기록은 본문과 원본 없이 기본 30일 보관한다.
|
||||
|
||||
## 11. 남용 방지와 용량
|
||||
|
||||
다음은 초기 정책 기본값이며 운영 설정으로 조정할 수 있다.
|
||||
|
||||
| 제한 | 초기 기본값 |
|
||||
|---|---|
|
||||
| 파일당 크기 | 2GiB |
|
||||
| 전송당 파일 또는 URL | 20개 |
|
||||
| 인증 계정 저장 용량 | 10GiB |
|
||||
| 익명 세션 저장 용량 | 2GiB |
|
||||
| 익명 세션당 동시 전송 | 1개 |
|
||||
| 계정당 동시 업로드 | 2개 |
|
||||
| 디스크 최소 여유 공간 | 5GiB |
|
||||
| 익명 요청 발신 | 세션당 분당 5회, IP당 분당 20회 |
|
||||
| 같은 익명 수신자에게 요청 | 송수신 쌍별 분당 2회 |
|
||||
| 원격 신규 수신 요청 | 발신 계정당 하루 20회 |
|
||||
| 미처리 원격 요청 | 수신 계정당 최대 50개 |
|
||||
|
||||
계정·익명 세션·IP·송수신 쌍을 함께 제한하고 익명 쿠키를 새로 만들어도 IP 제한을 우회하지 못하게 한다. 공유 IP의 정상 사용자 영향이 있으므로 운영에서 조정 가능하게 한다. 거절 후 같은 쌍은 기본 5분간 재요청을 제한한다. 수신자는 익명 상대를 세션 단위로 차단하고 발견 허용을 끌 수 있다. 익명 차단은 상대의 새 세션 생성까지 영구 식별하지 못한다는 한계를 명시한다.
|
||||
|
||||
익명 저장소에는 서비스 전체 20GiB 기본 상한을 추가하여 다수 세션 생성으로 NAS를 채우는 것을 제한한다. 수신 수락은 업로드 시작의 필수 조건이고 파일 크기 기반 용량 예약을 원자적으로 처리한다. 저장 공간 부족 시 수락·신규 업로드를 거절하고 기존 다운로드는 가능한 한 유지한다.
|
||||
|
||||
원본 파일은 송신 계정 또는 익명 세션의 사용량으로 계산하고 수신자가 즐겨찾기하면 수신자의 즐겨찾기 용량에도 논리 크기로 계산한다. 자기 보관함은 중복 합산하지 않는다. 실제 파일은 중복 저장하지 않아도 논리 용량은 계정별로 제한한다. 파일을 상대가 보존하는 동안 원본 소유자의 용량도 유지될 수 있음을 안내한다. 만료·삭제·재전송 참조와 용량 예약을 일관되게 계산한다.
|
||||
|
||||
이메일 인증만으로 남용을 막을 수 있다고 가정하지 않는다. 가입·로그인·메일 재발급·코드 대입에도 IP와 계정 기반 속도 제한을 둔다. 원격 발신자는 차단·신고할 수 있고 관리자에게 계정 정지와 신고 처리 화면을 제공한다. 신고는 식별자·사유 중심이며 본문·파일을 자동 복제하지 않는다. 초기에는 원격 요청마다 이메일 알림을 발송하지 않아 메일 폭탄을 방지한다. 인증·복구용 메일만 필수다.
|
||||
|
||||
## 12. 실시간 통신과 PWA
|
||||
|
||||
REST API와 DB가 상태의 기준이며 Socket.IO는 기기 발견·전송 요청·변경 알림을 전달한다. 서버가 인증한 익명 세션, 계정, 발견 범위 또는 공간으로 room을 구성하고 클라이언트가 임의 room에 가입할 수 없게 한다. 이벤트에는 필요한 최소 정보만 포함한다.
|
||||
|
||||
세션 만료·계정 정지·범위 변경·공간 퇴장 시 room과 권한을 다시 계산한다. 다중 탭 중 하나가 닫혔다고 기기 전체를 오프라인 처리하지 않는다. 재연결·탭 복귀 시 현재 요청과 수신 목록을 API로 다시 조회한다. 모바일 백그라운드나 브라우저 종료 시 온라인 상태를 보장하지 않는다.
|
||||
|
||||
PWA는 hop. 이름·아이콘·독립 실행·정적 리소스 캐시를 제공한다. 지원 플랫폼의 공유 메뉴에서는 파일·URL·텍스트를 보내기 초안으로 가져오고 사용자가 대상과 전송을 확정한다. 자동 전송은 하지 않는다. 미지원 브라우저는 붙여넣기·파일 선택으로 대체한다.
|
||||
|
||||
인증 API, 메시지, 파일, 수신자 정보는 공용 서비스 워커 캐시에 넣지 않는다. 로그아웃과 계정 전환 시 계정별 화면·초안·캐시를 정리한다. 익명 자료는 별도 수명 정책을 적용하고 공유 기기 초기화 동작을 제공한다. Web Push와 오프라인 새 전송은 초기 범위가 아니다.
|
||||
|
||||
## 13. UI 구성
|
||||
|
||||
미니멀하고 가벼운 전송 도구로 디자인한다. 라이트·다크·시스템 테마, 모바일 우선 반응형, 명확한 진행 상태와 오류 안내를 제공한다.
|
||||
|
||||
- 홈: hop., 내 기기 이름 변경, 발견 허용, 같은 네트워크 기기 카드, QR·코드 연결, 빠른 전송. 로그인 없이 접근한다.
|
||||
- 즉석 보내기: 대상 기기, 파일·텍스트·URL, 수락 대기·전송 진행·결과.
|
||||
- 즉석 받기: 조용한 요청 카드, 수락·거절·차단, 전송 후 복사·열기·다운로드.
|
||||
- 이번 기기: 익명 전송·수신 목록과 삭제, 로그인 후 선택 자료 저장.
|
||||
- 이메일로 보내기: 로그인 안내 또는 이메일 입력, 요청인지 즉시 전송인지 상태 안내.
|
||||
- 계정 보관함: 받은 항목·수신 요청·내 보관함·보낸 항목·즐겨찾기.
|
||||
- 계정 화면: 가입·인증·로그인·복구, 이메일·비밀번호, 허용·차단, 용량, 세션, 탈퇴.
|
||||
- 설정: 현재 기기 이름, 발견 허용, 테마, 익명 기기 초기화.
|
||||
- 관리자: 가입 모드·계정 정지·신고·용량 제한·정리 작업과 저장소 상태.
|
||||
|
||||
데스크톱은 기기 카드와 전송 패널을 중심으로 구성한다. 모바일 하단 내비게이션은 ‘기기’, ‘이번 기기’, ‘보관함’으로 하고 로그인은 보관함과 이메일 전송에서만 요구한다. 주변 전송보다 계정 가입·보관함을 더 크게 강조하지 않는다. 코드 연결은 네트워크가 달라도 쓸 수 있도록 눈에 띄게 제공한다.
|
||||
|
||||
## 13.1 가벼운 진입 흐름
|
||||
|
||||
첫 접속에서 회원가입 모달, 이름 입력 강제, 튜토리얼, 알림 권한 요청을 띄우지 않는다. 기본 기기 이름으로 곧바로 사용하고 이름 변경은 홈에서 한 번의 조작으로 접근한다. 위치·Bluetooth·연락처 권한을 요구하지 않는다. 로그인 버튼은 보조로 배치한다.
|
||||
|
||||
발견 가능한 기기가 없으면 ‘상대도 hop.을 열어주세요’와 QR·코드 연결을 보여준다. 회원가입을 해결책으로 먼저 제시하지 않는다. 파일·텍스트·URL은 하나의 전송 입력 영역에서 쉽게 선택하고 드래그 또는 붙여넣기로 시작할 수 있게 한다. 보안 설정의 복잡한 용어를 일반 사용자 흐름에 노출하지 않는다. 수락·거절과 상태 안내는 짧고 분명한 문구를 사용한다.
|
||||
|
||||
## 13.2 광고 수익화
|
||||
|
||||
메인 화면에 광고를 배치할 수 있도록 독립적인 광고 슬롯을 설계한다. 초기 구현은 광고 기능을 꺼둔 상태로 제공하며 실제 광고 계정·코드가 준비되면 운영자가 활성화한다. 특정 사업자의 수익이나 승인을 보장하지 않는다. 광고 사업자 연동 및 공개 시점의 정책·동의 요건 검토는 실제 활성화 단계에서 수행한다.
|
||||
|
||||
- 데스크톱은 기기·전송 영역 바깥의 보조 영역, 모바일은 핵심 동작 아래 별도 영역에 한 개의 기본 슬롯을 둔다.
|
||||
- ‘광고’라고 명확히 표시하고 전송·다운로드·수락 버튼처럼 보이는 자체 UI를 만들지 않는다.
|
||||
- 전면 광고, 강제 시청, 다운로드 전 광고 클릭, 팝업 광고, 수락 대화상자 안 광고는 제공하지 않는다.
|
||||
- 전송 진행·수신 요청·파일 상세·로그인·계정 보관함 화면에는 광고를 표시하지 않는다. 광고 로딩 실패나 차단이 주요 기능을 막아서는 안 된다.
|
||||
- 슬롯 공간을 예약하여 늦은 로딩이 전송 버튼 위치를 바꾸거나 오터치를 유발하지 않게 한다. 광고 스크립트는 핵심 앱 로딩을 차단하지 않는다.
|
||||
- 파일명·본문·공유 URL·이메일·기기 이름·공간 코드·접속 IP 그룹을 광고 타기팅 데이터나 광고 요청 매개변수로 보내지 않는다.
|
||||
- 광고 스크립트가 같은 페이지에서 실행되면 그 자체로 개인정보 경계가 된다고 가정하지 않는다. 사업자가 지원하는 격리 방식과 최소한의 비민감 홈 상태를 검토하고, 계정·요청·내용 화면으로 진입할 때 광고를 제거하거나 별도 비광고 문서로 전환한다.
|
||||
- 제3자 광고가 활성화되면 ‘외부 서비스 의존 없음’ 또는 ‘접속 정보가 외부로 전혀 전달되지 않음’이라고 안내하지 않는다. 해당 사업자 수집과 필요한 동의·설정을 실제 구성에 맞춰 공개한다.
|
||||
|
||||
광고 없이도 핵심 기능을 완전히 사용할 수 있어야 한다. 유료 구독이나 파일 보관 업셀은 초기 범위에 추가하지 않는다.
|
||||
|
||||
## 14. 기술 아키텍처
|
||||
|
||||
| 영역 | 기술 |
|
||||
|---|---|
|
||||
| Frontend | Vue 3, TypeScript, Vite, Tailwind CSS |
|
||||
| Backend | Node.js, TypeScript, Fastify |
|
||||
| 실시간 | Socket.IO |
|
||||
| DB | SQLite, Drizzle ORM, 마이그레이션 |
|
||||
| 파일 | NAS 비공개 영속 볼륨 |
|
||||
| 인증 | 익명 세션과 계정 서버 세션, Argon2id |
|
||||
| 메일 | 설정 가능한 SMTP |
|
||||
| 배포 | Docker Compose, Nginx Proxy Manager, HTTPS |
|
||||
|
||||
apps/web, apps/server, packages/shared로 단일 저장소를 구성한다. 단일 NAS에서 시작하고 DB·저장소·메일 모듈을 분리한다. Redis·PostgreSQL·외부 저장소·다중 서버는 초기 필수가 아니며 공개 이용량 확대 시 별도 검증한다.
|
||||
|
||||
## 15. 데이터 모델
|
||||
|
||||
내부 UUID와 공개 이름·이메일을 분리한다. 시간은 UTC 저장, UI는 현지 시간으로 표시한다. 외래 키, 유일 제약, 만료·목록 조회 인덱스를 적용한다.
|
||||
|
||||
| 테이블 | 주요 역할 |
|
||||
|---|---|
|
||||
| users | 이메일, 표시 이름, 비밀번호 해시, 역할·상태·인증 시간 |
|
||||
| account_sessions | 계정 세션 해시, 만료·폐기·최종 접속 |
|
||||
| guest_sessions | 익명 세션 해시, 만료·폐기 |
|
||||
| browser_devices | 익명 세션에 속한 기기 이름, 발견 허용, 최종 접속 |
|
||||
| connection_spaces | 생성 세션, QR 토큰 해시, 코드 해시, 만료 |
|
||||
| space_members | 공간·기기, 승인·퇴장 상태 |
|
||||
| account_tokens | 인증·복구·이메일 변경용 일회용 토큰 해시 |
|
||||
| transfer_requests | local/email 모드, 발신 주체, 수신 주체, 메타데이터, 수락·만료·취소 |
|
||||
| transfer_batches | 요청 참조, 저장 상태, idempotency key, request hash |
|
||||
| items | 묶음, 유형, 본문·URL·파일 참조, 저장·만료 시각 |
|
||||
| item_access | 항목, 계정 또는 익명 주체, sender/recipient/self 역할, 읽음·즐겨찾기·삭제 |
|
||||
| files | 소유 주체, 저장 키, 파일명·타입·크기·체크섬·상태 |
|
||||
| user_rules | 허용·차단 관계와 수신 정책 |
|
||||
| quota_reservations | 업로드·주체·예약 바이트·만료 |
|
||||
| deletion_operations | 삭제 주체·복원 기한·대상·복원 결과 |
|
||||
| reports | 신고자·발신자·요청 또는 항목·사유·처리 |
|
||||
| system_settings | 운영 정책과 제한 |
|
||||
| audit_events | 최소 행위·식별자·결과·시간 |
|
||||
|
||||
주체 필드는 user_id와 guest_session_id 중 정확히 하나만 값이 있도록 CHECK 제약을 적용한다. 접근권은 원본 항목과 분리하고 역할별 중복을 막는다. 온라인 상태는 활성 Socket 연결에서 계산하며 DB의 과거 last_seen만으로 온라인을 판단하지 않는다.
|
||||
|
||||
발견 IP 그룹은 접속 중 임시 서버 상태로 유지하고 공개 room 이름에 원본 IP를 넣지 않는다. 요청 수락·용량 예약·전송 확정·접근권 생성은 트랜잭션과 상태 전이 검증을 사용한다. 재전송은 새로운 전송·만료를 만들며 접근 가능한 기존 파일을 참조할 수 있다. 물리 정리는 모든 참조와 유예를 검사한다.
|
||||
|
||||
탈퇴 시 로그인 개인정보·토큰·세션·본인 접근권을 정리한다. 다른 수신자의 유효한 전달 자료는 자체 만료까지 유지하고 발신자를 ‘탈퇴한 사용자’로 표시한다. 이 보존 정책은 탈퇴 전에 안내한다. 익명 세션 정리도 상대의 유효한 접근권을 삭제하지 않는다.
|
||||
|
||||
## 16. API와 이벤트
|
||||
|
||||
```text
|
||||
POST /api/guest/session
|
||||
GET /api/device/me
|
||||
PATCH /api/device/me
|
||||
POST /api/device/reset
|
||||
GET /api/discovery/devices
|
||||
POST /api/spaces
|
||||
POST /api/spaces/join
|
||||
POST /api/spaces/:id/members/:memberId/approve
|
||||
DELETE /api/spaces/:id/members/:memberId
|
||||
DELETE /api/spaces/:id
|
||||
|
||||
POST /api/auth/setup
|
||||
POST /api/auth/register
|
||||
POST /api/auth/verify-email
|
||||
POST /api/auth/resend-verification
|
||||
POST /api/auth/login
|
||||
POST /api/auth/logout
|
||||
GET /api/auth/me
|
||||
POST /api/auth/forgot-password
|
||||
POST /api/auth/reset-password
|
||||
POST /api/auth/change-password
|
||||
POST /api/auth/change-email
|
||||
GET /api/account/sessions
|
||||
DELETE /api/account/sessions/:id
|
||||
DELETE /api/account
|
||||
|
||||
POST /api/transfer-requests
|
||||
GET /api/transfer-requests
|
||||
POST /api/transfer-requests/:id/accept
|
||||
POST /api/transfer-requests/:id/reject
|
||||
POST /api/transfer-requests/:id/cancel
|
||||
POST /api/uploads
|
||||
DELETE /api/uploads/:id
|
||||
POST /api/transfers/:id/complete
|
||||
|
||||
GET /api/device/items
|
||||
GET /api/inbox
|
||||
GET /api/vault
|
||||
GET /api/sent
|
||||
GET /api/favorites
|
||||
GET /api/access/:id
|
||||
PATCH /api/access/:id/read
|
||||
PATCH /api/access/:id/favorite
|
||||
POST /api/access/delete-batch
|
||||
POST /api/deletion-operations/:id/undo
|
||||
POST /api/access/:id/save-to-account
|
||||
POST /api/access/:id/resend
|
||||
GET /api/access/:id/download
|
||||
GET /api/access/:id/preview
|
||||
|
||||
GET /api/account/preferences
|
||||
PATCH /api/account/preferences
|
||||
GET /api/account/rules
|
||||
PUT /api/account/rules/:targetUserId
|
||||
DELETE /api/account/rules/:targetUserId
|
||||
GET /api/account/storage
|
||||
POST /api/reports
|
||||
GET /api/admin/users
|
||||
PATCH /api/admin/users/:id
|
||||
GET /api/admin/settings
|
||||
PATCH /api/admin/settings
|
||||
GET /api/admin/reports
|
||||
PATCH /api/admin/reports/:id
|
||||
GET /api/health
|
||||
```
|
||||
|
||||
API는 익명 허용·계정 필요·관리자 필요를 명시적으로 분리한다. 익명 허용은 인증 불필요를 뜻하지 않으며 서버 익명 세션과 대상 권한을 검증한다. API 목록은 커서 페이지네이션, 오류는 code/message/requestId를 사용한다. read/favorite은 목표 값을 받아 재시도 가능한 상태 변경으로 처리한다.
|
||||
|
||||
Socket.IO 이벤트: device:online, device:offline, device:updated, space:updated, request:created, request:updated, transfer:updated, inbox:updated, access:updated, account:revoked. 파일 바이트는 Socket.IO 이벤트로 보내지 않는다. 클라이언트 업로드 진행률과 서버 저장 확정은 구분한다.
|
||||
|
||||
## 17. 보안 요구사항
|
||||
|
||||
HTTPS, 안전한 쿠키, CSRF 토큰·Origin 검증, Socket.IO 인증·Origin 검증을 적용한다. 모든 항목·파일·요청·공간·이벤트에서 계정 또는 익명 세션 접근권을 확인한다. 리소스 ID·기기 ID·공간 ID는 인증 비밀값이 아니다.
|
||||
|
||||
프록시의 실제 클라이언트 IP 해석은 명시적으로 신뢰한 Nginx Proxy Manager와 필요한 상위 프록시만 허용한다. 임의 X-Forwarded-For로 발견 범위와 rate limit을 우회하지 못하게 한다. Cloudflare를 경유한다면 신뢰 가능한 원본 IP 전달을 실제 환경에서 검증한다. 검증된 IP를 얻을 수 없으면 같은 네트워크 탐색을 끄고 QR·코드로 대체한다. 모든 사용자를 프록시 IP 하나로 묶어 노출하는 구현은 금지한다.
|
||||
|
||||
입력 검증, XSS 방어, CSP, 경로 조작 방어, 비공개 파일 저장, SSRF 방어, 업로드 제한을 구현한다. 로그에 세션·토큰·메시지·민감 URL을 남기지 않는다. QR 초대 토큰이 로그·리퍼러로 새지 않도록 처리하고 가입·로그인 페이지가 토큰을 외부로 전송하지 않게 한다.
|
||||
|
||||
서비스 공개 전에 가입 모드, SMTP, 요청 제한, 익명 저장소 상한, 백업·복구, 개인정보 안내와 신고·정지 기능을 설정한다. 사용자에게 E2EE나 물리적 근접 탐지를 제공한다고 표현하지 않는다.
|
||||
|
||||
## 18. Docker와 운영
|
||||
|
||||
```text
|
||||
hop/
|
||||
apps/web/
|
||||
apps/server/
|
||||
packages/shared/
|
||||
docker/
|
||||
compose.yaml
|
||||
Dockerfile
|
||||
.env.example
|
||||
SPEC.md
|
||||
README.md
|
||||
```
|
||||
|
||||
```env
|
||||
NODE_ENV=production
|
||||
TZ=Asia/Seoul
|
||||
PORT=3000
|
||||
APP_URL=https://hop.noname.company
|
||||
DATABASE_PATH=/data/database/hop.sqlite
|
||||
UPLOAD_DIR=/data/uploads
|
||||
SESSION_SECRET=replace-with-a-long-random-secret
|
||||
DISCOVERY_SECRET=replace-with-another-random-secret
|
||||
REGISTRATION_MODE=closed
|
||||
EMAIL_VERIFICATION_REQUIRED=true
|
||||
SAME_NETWORK_DISCOVERY_ENABLED=true
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_FROM=
|
||||
MAX_FILE_SIZE_MB=2048
|
||||
MAX_TEXT_SIZE_KB=1024
|
||||
USER_STORAGE_LIMIT_MB=10240
|
||||
GUEST_STORAGE_LIMIT_MB=2048
|
||||
TOTAL_GUEST_STORAGE_LIMIT_MB=20480
|
||||
MIN_FREE_DISK_MB=5120
|
||||
MAX_ITEMS_PER_TRANSFER=20
|
||||
DEFAULT_FILE_RETENTION_DAYS=7
|
||||
DEFAULT_TEXT_RETENTION_DAYS=30
|
||||
DEFAULT_URL_RETENTION_DAYS=30
|
||||
LOCAL_REQUEST_TIMEOUT_SECONDS=60
|
||||
EMAIL_REQUEST_RETENTION_DAYS=7
|
||||
ACCEPTED_UPLOAD_START_HOURS=24
|
||||
SPACE_RETENTION_MINUTES=10
|
||||
SPACE_MAX_DEVICES=10
|
||||
GUEST_SESSION_RETENTION_DAYS=30
|
||||
ORPHAN_UPLOAD_RETENTION_HOURS=24
|
||||
DELETE_UNDO_SECONDS=5
|
||||
AUDIT_RETENTION_DAYS=30
|
||||
```
|
||||
|
||||
MB/KB 변수는 MiB/KiB 기준으로 계산하고 README에 표기한다. rate limit, 동시 업로드, 프록시 신뢰 범위, SMTP TLS와 정리 주기를 추가 설정 가능하게 한다. 비밀값과 실제 데이터는 Git에 포함하지 않는다.
|
||||
|
||||
Compose로 실행·마이그레이션하고 DB·파일은 영속 볼륨으로 분리한다. 자동 재시작·health check·로그를 제공한다. Nginx Proxy Manager의 HTTPS·WebSocket·업로드 크기와 시간 제한을 문서화한다. 앱의 2GiB 한도는 Cloudflare와 중간 프록시 한도를 보장하지 않으므로 실제 배포 경로에서 검증하고 필요한 직접 업로드 경로 또는 상한 조정을 문서화한다.
|
||||
|
||||
README에는 설치, 관리자 초기화, 가입 열기·닫기, SMTP, 발견 범위와 프록시 IP 설정, QR 연결, 업데이트, 백업·복구, 공간 부족·정리 실패 대응, 익명 자료 접근 제한을 포함한다. DB·파일의 일관된 백업과 복원 검증을 제공한다.
|
||||
|
||||
## 19. 구현 단계
|
||||
|
||||
### Phase 1 가입 없는 즉석 전송
|
||||
|
||||
프로젝트·Docker, 익명 세션, 이름 변경, 발견 허용, 검증된 IP 그룹, QR·코드 공간과 선택적 참여 승인, 온라인 상태, 요청 수락·거절, 텍스트·URL 전송, 이번 기기 목록을 구현한다. 범위 권한 검사와 요청 제한은 이 단계부터 적용한다.
|
||||
|
||||
### Phase 2 파일과 삭제
|
||||
|
||||
수락 이후 스트리밍 업로드, 진행률·취소, 인증 다운로드·미리보기, 익명 용량·전체 상한, 만료·고아 정리, 밀어서 삭제·실행 취소, 실제 모바일 동작을 구현한다.
|
||||
|
||||
### Phase 3 이메일 원격 전송
|
||||
|
||||
이메일 가입·인증·로그인·복구, 수신 요청·허용·차단, 오프라인 저장, 계정 보관함·내 보관함, 읽음·즐겨찾기·검색, 계정 저장 용량과 익명 자료 선택 저장을 구현한다.
|
||||
|
||||
### Phase 4 편의와 운영
|
||||
|
||||
PWA·공유 메뉴 초안, URL 미리보기, 테마, 비활성 기본 광고 슬롯, 재전송, 계정 세션 관리·탈퇴, 신고·관리자·가입 모드와 백업 문서를 완성한다.
|
||||
|
||||
### Phase 5 검증
|
||||
|
||||
익명·계정 간 권한 격리, 발견 범위·프록시 IP, 코드 대입 제한, 동시 용량 예약, 요청 상태 전이, 파일 중단, 삭제·복원·만료·즐겨찾기 정리, 재연결, 모바일·Docker 데이터 보존과 백업 복원을 검증한다.
|
||||
|
||||
## 20. 완료 기준
|
||||
|
||||
1. 로그인 없이 사이트를 열어 기기 이름을 변경하고 재접속 후 유지할 수 있다.
|
||||
2. 같은 네트워크 목록에 전체 사이트 이용자가 노출되지 않고 발견 끄기가 즉시 반영된다.
|
||||
3. 다른 네트워크의 기기를 QR·코드로 연결하며 선택적 참여 승인·퇴장·만료가 적용된다.
|
||||
4. 여러 탭은 하나의 기기로 보이고 마지막 연결 종료 시 오프라인 처리된다.
|
||||
5. 수락 전에 파일 업로드를 시작할 수 없고 거절·타임아웃 시 전달하지 않는다.
|
||||
6. 익명 기기 간 파일·텍스트·URL을 실제로 전달하고 복사·다운로드할 수 있다.
|
||||
7. 이메일 인증 계정으로 원격 요청을 보내고 첫 수락 이후 전달을 완료할 수 있다.
|
||||
8. 허용된 발신자는 수신자가 오프라인이어도 저장할 수 있다.
|
||||
9. 상대가 수락해도 송신자가 오프라인이면 전송 대기 상태를 정확히 안내한다.
|
||||
10. 같은 계정의 여러 기기에서 동일한 계정 목록과 읽음·삭제 상태를 공유한다.
|
||||
11. 밀어서 삭제가 스크롤과 충돌하지 않고 5초 실행 취소·서버 실패 복구가 작동한다.
|
||||
12. 한쪽 목록 삭제가 상대의 자료를 회수하지 않는다.
|
||||
13. 보관 기간·개별 즐겨찾기·마지막 접근권 정리가 일관되게 적용된다.
|
||||
14. 다른 세션의 기기·파일·공간·계정 항목 ID를 알아도 무단 접근할 수 없다.
|
||||
15. 동시·중복 요청이 업로드 제한·용량 제한·수신 정책을 우회하지 못한다.
|
||||
16. 로그아웃·계정 전환과 익명 초기화가 정해진 데이터 경계를 유지한다.
|
||||
17. Docker 재배포·NAS 재시작 후 자료가 유지되고 백업에서 복구할 수 있다.
|
||||
18. 첫 접속에서 가입·이름 입력·권한 허용 없이 즉석 전송을 시작할 수 있다.
|
||||
19. 광고 비활성·로딩 실패·차단 상태에서도 전송과 수신이 정상 동작하고 광고 때문에 핵심 버튼 위치가 바뀌지 않는다.
|
||||
|
||||
## 21. 초기 제외 범위와 개발 지침
|
||||
|
||||
Bluetooth 기반 물리적 근접 탐색, WebRTC P2P, 기기별 영구 계정 수신함, 비회원 이메일 전송·초대, 공개 다운로드 링크, 네이티브 앱, 자동 클립보드 동기화, E2EE, Web Push, 파일 버전 관리, 전송 회수, 다중 이메일 동시 전송, 요금제와 다중 서버는 초기 범위에서 제외한다.
|
||||
|
||||
이 문서를 기준으로 실제 API·DB·파일 저장을 연결한 앱을 개발한다. 먼저 아키텍처, 익명·계정 권한 모델, 요청·업로드 상태 전이, 정리·용량 규칙과 단계별 계획을 제시한다. Phase 1부터 구현하며 빌드와 관련 테스트를 단계별 실행한다. 더미 데이터로 기능 완료를 주장하지 않는다.
|
||||
|
||||
홈의 중심은 가입 없는 즉석 전송이며 계정 기능 때문에 진입 흐름을 바꾸지 않는다. 기기 이름 변경은 필수다. 브라우저의 기술적 제약이나 배포 프록시 제한을 숨기지 않는다. 실기기에서 확인하지 못한 모바일·PWA 동작은 검증 범위를 구분해 보고한다. 명세 밖의 중요한 제품 범위 변경은 구현 전에 질문한다. 코드·설치 문서·환경 예시·검증 결과·알려진 제한을 함께 제공한다.
|
||||
Reference in New Issue
Block a user