fix: 공유 링크 페이지 이미지 저장 기기 분기 및 CORS 수정 - #159
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- RootLayout async 제거, SessionBridge로 분리하여 metadata streaming 문제 해결 - layout.tsx openGraph 이미지 URL 절대경로로 수정 - disputes/[id]/type/page.tsx OG fallback URL 절대경로로 수정 - JudgmentTypeResult 다운로드 버튼 anchor download 방식으로 통일 - kakaoShare.ts 미사용 파일 삭제 - invite/route.ts NEXT_PUBLIC_APP_URL → NEXT_PUBLIC_BASE_URL 교체 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- invite/route.ts: NEXT_PUBLIC_BASE_URL 누락 시 프로덕션에서 CONFIG_ERROR 500 반환 - invite/route.ts: template literal → new URL()로 URL 조합 안전성 개선 - layout.tsx: OG 이미지 URL 하드코딩 제거, NEXT_PUBLIC_BASE_URL 기반으로 변경 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- layout.tsx, invite/route.ts, type/page.tsx 모두 NEXT_PUBLIC_APP_URL로 일관 적용 - type/page.tsx 하드코딩된 OG 이미지 URL 제거 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- layout.tsx: metadataBase 복원, NEXT_PUBLIC_APP_URL 적용, SessionBridge 분리로 metadata streaming 수정 - type/page.tsx: NEXT_PUBLIC_BASE_URL → NEXT_PUBLIC_APP_URL 통일 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dev 복원 과정에서 participants 쿼리와 ownerName 반환값이 빠진 것 수정 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Apple 기기(iOS/iPad): 버튼 1개 '공유 및 저장' - navigator.share로 링크+이미지 함께 공유 - 비Apple 767px 이상: 공유하기 클릭 시 링크 복사 + 토스트 메시지 - 비Apple 767px 미만: navigator.share로 URL 공유 - 이미지 파일명 한글 제거 → talkyowl-conflict-type.jpg Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- /api/download 프록시 엔드포인트 추가 (CORS 우회, 자동 다운로드) - handleDownload: 직접 Supabase URL 대신 /api/download 프록시 경유 - handleShare: 767px 이상에서 링크 복사 + 토스트 메시지 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- download/route.ts: 전체 로직 try-catch로 감싸기 - download/route.ts: 업스트림 Content-Type 검증 추가 (JPEG 아닌 경우 415 반환) - JudgmentTypeResult: handleAppleShareSave도 /api/download 프록시 경유로 통일 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 호스트 검증: SUPABASE_URL 고정 비교 → *.supabase.co 패턴으로 변경 (Storage URL 호스트 불일치 해결) - Content-Type 검증: image/jpeg 전용 → image/* 전체 허용 (WebP 등 대응) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Supabase URL 직접 fetch → /api/download 프록시 경유로 변경 (CORS 해결)
- isAppleDevice UserAgent 감지 추가
- Apple: navigator.share({ files }) → 공유 시트 → 사진에 저장
- 비Apple: a download → 공유 시트 없이 바로 저장
- 파일명 한글 제거, 고정 영문(talkyowl-conflict-type.jpg)으로 통일
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthrough이미지 다운로드/공유 흐름이 Changes이미지 다운로드/공유 흐름
Sequence Diagram(s)sequenceDiagram
participant Client as ConflictTypeClient
participant DownloadAPI as /api/download
participant Share as navigator.share
participant OpenTab as window.open
participant Anchor as HTMLAnchorElement
participant URLApi as URL
Client->>DownloadAPI: fetch blob via /api/download?url=...
DownloadAPI-->>Client: image blob
alt Apple device
Client->>Share: share({ files })
alt AbortError
Share-->>Client: ignored
else share failure
Client->>OpenTab: open original image
end
else non-Apple device
Client->>URLApi: createObjectURL(blob)
Client->>Anchor: click download
Client->>URLApi: revokeObjectURL()
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/`(page)/disputes/[id]/type/ConflictTypeClient.tsx:
- Around line 26-40: Narrow the Apple-specific branch in ConflictTypeClient’s
handleDownload so only iOS/iPadOS devices enter the share flow, and avoid
treating generic Macintosh as share-capable. Before calling navigator.share({
files }) in the isAppleDevice path, first verify support with
navigator.canShare({ files }) and fall back to the direct download path when
sharing isn’t available. Use the existing handleDownload and isAppleDevice logic
to locate and update the device-detection and share checks.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: cf20b649-ebd3-48c2-9cc3-388da9b04f75
📒 Files selected for processing (1)
src/app/(page)/disputes/[id]/type/ConflictTypeClient.tsx
| setIsAppleDevice(/iPhone|iPad|iPod|Macintosh/.test(navigator.userAgent)) | ||
| }, []) | ||
|
|
||
| const handleDownload = async () => { | ||
| if (!data?.cardImageUrl) return | ||
| try { | ||
| const res = await fetch(data.cardImageUrl) | ||
| if (!res.ok) throw new Error(`Image download failed: ${res.status}`) | ||
| const proxyUrl = `/api/download?url=${encodeURIComponent(data.cardImageUrl)}` | ||
| const res = await fetch(proxyUrl) | ||
| if (!res.ok) throw new Error(`fetch failed: ${res.status}`) | ||
| const blob = await res.blob() | ||
| const fileName = `갈등유형_${data.displayName}.jpg` | ||
| const file = new File([blob], fileName, { type: blob.type || 'image/jpeg' }) | ||
|
|
||
| if (navigator.canShare?.({ files: [file] })) { | ||
| if (isAppleDevice) { | ||
| const file = new File([blob], 'talkyowl-conflict-type.jpg', { type: 'image/jpeg' }) | ||
| try { | ||
| await navigator.share({ files: [file] }) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
git ls-files 'src/app/(page)/disputes/[id]/type/ConflictTypeClient.tsx'
echo '---'
sed -n '1,220p' 'src/app/(page)/disputes/[id]/type/ConflictTypeClient.tsx'Repository: I5-Project/TALKY-OWL
Length of output: 3749
🏁 Script executed:
#!/bin/bash
set -euo pipefail
wc -l 'src/app/(page)/disputes/[id]/type/ConflictTypeClient.tsx'
echo '---'
nl -ba 'src/app/(page)/disputes/[id]/type/ConflictTypeClient.tsx' | sed -n '1,220p'Repository: I5-Project/TALKY-OWL
Length of output: 263
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "navigator\.share|canShare|Macintosh|iPhone|iPad|iPod|cardImageUrl|handleDownload|isAppleDevice" 'src/app/(page)/disputes/[id]/type/ConflictTypeClient.tsx'Repository: I5-Project/TALKY-OWL
Length of output: 800
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('src/app/(page)/disputes/[id]/type/ConflictTypeClient.tsx')
print(p.exists())
print(p.read_text()[:4000])
PYRepository: I5-Project/TALKY-OWL
Length of output: 3694
Apple 분기 조건을 iOS 전용으로 좁히고, 파일 공유 지원 여부를 먼저 확인하세요. Macintosh를 포함하면 macOS도 공유 경로로 들어가며, navigator.share({ files })는 navigator.canShare({ files }) 체크 없이 호출하면 일부 브라우저에서 바로 실패합니다. iPhone/iPad/iPod(또는 iPadOS touch Mac 판별)만 공유 분기로 보내고, 그 외에는 직접 다운로드로 처리하세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/`(page)/disputes/[id]/type/ConflictTypeClient.tsx around lines 26 -
40, Narrow the Apple-specific branch in ConflictTypeClient’s handleDownload so
only iOS/iPadOS devices enter the share flow, and avoid treating generic
Macintosh as share-capable. Before calling navigator.share({ files }) in the
isAppleDevice path, first verify support with navigator.canShare({ files }) and
fall back to the direct download path when sharing isn’t available. Use the
existing handleDownload and isAppleDevice logic to locate and update the
device-detection and share checks.
Summary
/disputes/[id]/type)의ConflictTypeClient에서 이미지 저장 시 Supabase URL 직접 fetch → CORS 차단으로 저장 불가 문제 수정/api/download서버 프록시 경유로 변경navigator.share({ files })→ 공유 시트 → 사진에 저장<a download>→ 공유 시트 없이 바로 저장talkyowl-conflict-type.jpg고정 영문으로 통일Changed Files
src/app/(page)/disputes/[id]/type/ConflictTypeClient.tsxTest plan
🤖 Generated with Claude Code
Summary by CodeRabbit