Skip to content

fix: 공유 링크 페이지 이미지 저장 기기 분기 및 CORS 수정 - #159

Merged
evenif99 merged 32 commits into
devfrom
feature/error-pages
Jun 26, 2026
Merged

fix: 공유 링크 페이지 이미지 저장 기기 분기 및 CORS 수정#159
evenif99 merged 32 commits into
devfrom
feature/error-pages

Conversation

@wjdalss21

@wjdalss21 wjdalss21 commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • 공유 링크 페이지(/disputes/[id]/type)의 ConflictTypeClient에서 이미지 저장 시 Supabase URL 직접 fetch → CORS 차단으로 저장 불가 문제 수정
  • /api/download 서버 프록시 경유로 변경
  • Apple 기기(iOS) / 비Apple 기기(Android, PC) 분기 추가
    • Apple: navigator.share({ files }) → 공유 시트 → 사진에 저장
    • 비Apple: <a download> → 공유 시트 없이 바로 저장
  • 파일명 한글 제거 → talkyowl-conflict-type.jpg 고정 영문으로 통일

Changed Files

  • src/app/(page)/disputes/[id]/type/ConflictTypeClient.tsx

Test plan

  • iOS에서 공유 링크 접속 후 "결과 다운받기" → 공유 시트 뜨고 "사진에 저장" 선택 시 갤러리 저장 확인
  • Android에서 공유 링크 접속 후 "결과 다운받기" → 공유 시트 없이 바로 저장 확인
  • PC에서 공유 링크 접속 후 "결과 다운받기" → 다운로드 폴더에 저장 확인

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 기능 개선
    • 이미지 다운로드 및 공유 방식이 개선되었습니다.
    • Apple 기기에서는 공유 시트를 통해 파일 공유를 우선 시도하고, 실패 시 원본 이미지를 새 탭으로 열 수 있습니다.
    • Apple 기기가 아닌 환경에서는 파일을 바로 다운로드하며, 임시 파일 URL 정리도 적용되었습니다.
    • 다운로드 파일명이 고정되어 더 일관된 이름으로 저장됩니다.

wjdalss21 and others added 30 commits June 23, 2026 18:28
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>
wjdalss21 and others added 2 commits June 26, 2026 13:26
- 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>
@vercel

vercel Bot commented Jun 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
talky-owl Ready Ready Preview, Comment Jun 26, 2026 8:31am

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

이미지 다운로드/공유 흐름이 /api/download 프록시를 통해 blob을 받도록 바뀌었습니다. Apple 기기에서는 navigator.share를 우선 사용하고, 실패 시 원본 이미지를 새 탭으로 엽니다. 비-Apple 기기에서는 object URL로 다운로드하고 해제합니다.

Changes

이미지 다운로드/공유 흐름

Layer / File(s) Summary
다운로드/공유 분기
src/app/(page)/disputes/[id]/type/ConflictTypeClient.tsx
useState/useEffect로 Apple 기기 여부를 계산하고, handleDownload/api/download 프록시 blob을 사용해 Apple에서는 navigator.share와 원본 새 탭 폴백을, 그 외에는 <a download>와 object URL 해제를 수행합니다.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • I5-Project/TALKY-OWL#124ConflictTypeClient의 다운로드/공유 경로를 다루는 이전 변경입니다.
  • I5-Project/TALKY-OWL#152 — 클라이언트 측 이미지/파일 다운로드와 object URL 사용 흐름이 연결됩니다.
  • I5-Project/TALKY-OWL#153/api/download 라우트와 이 PR의 프록시 기반 처리 흐름이 직접 맞닿아 있습니다.

Suggested reviewers

  • evenif99
  • wjdals21

Poem

깡총깡총 토끼가 봤어요,
프록시 길로 blob이 폴짝!
사과 기기엔 share를 건네고,
아니면 다운로드를 척척,
object URL도 잠깐 놀다 휙 🐰

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning 요약과 테스트 계획은 있으나 템플릿의 작업 내용, 관련 Issue, 변경 사항, 체크리스트 등 주요 섹션이 대부분 비어 있습니다. 템플릿의 1~10번 항목을 채우고, 관련 Issue/FR/NFR, 변경 사항, 테스트 결과, 보안·DB/API 영향 여부를 구체적으로 추가하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 공유 링크 페이지 이미지 저장 방식 변경과 CORS 수정이라는 핵심 변경을 잘 요약합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/error-pages

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between fd984f0 and 2329b2a.

📒 Files selected for processing (1)
  • src/app/(page)/disputes/[id]/type/ConflictTypeClient.tsx

Comment on lines +26 to 40
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] })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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])
PY

Repository: 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.

@evenif99
evenif99 merged commit 0da2b5f into dev Jun 26, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants