-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 앱 공유 바텀시트 에러 상태 분기 #501
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,28 @@ import { postTokenRefresh } from '@/apis/postTokenRefresh'; | |
|
|
||
| import { TokenStorage } from './tokenStorage'; | ||
|
|
||
| type PostWishLinkFromShareResultT = { ok: true } | { ok: false; message: string }; | ||
| /** | ||
| * 실패 사유. 문구는 시트가 정하고 여기서는 사유만 식별한다. | ||
| * - `unauthenticated` 토큰 없음 → 로그인 유도 | ||
| * - `sessionExpired` refresh 실패 → 로그인 유도 | ||
| * - `network` 네트워크 예외 → 재시도 가능 | ||
| * - `server` 서버 오류 → 재시도 가능 | ||
| */ | ||
| export type ShareFailureReasonT = 'unauthenticated' | 'sessionExpired' | 'network' | 'server'; | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| export type PostWishLinkFromShareResultT = | ||
| | { ok: true } | ||
| | { | ||
| ok: false; | ||
| reason: ShareFailureReasonT; | ||
| /** | ||
| * 다시 시도할 가치가 있는지. 같은 reason 이라도 갈린다 — | ||
| * 서버 5xx 는 재시도 가능하지만 설정 누락은 몇 번을 눌러도 같다. | ||
| */ | ||
| retryable: boolean; | ||
| /** 서버가 내려준 에러 코드 (로컬 판단 실패면 없음) */ | ||
| code?: string; | ||
| }; | ||
|
|
||
| const postWishLink = async (productUrl: string, accessToken: string) => | ||
| fetch(`${process.env.EXPO_PUBLIC_API_URL}/api/v1/wishlists`, { | ||
|
|
@@ -15,17 +36,27 @@ const postWishLink = async (productUrl: string, accessToken: string) => | |
| body: JSON.stringify({ url: productUrl }), | ||
| }); | ||
|
|
||
| /** 실패 응답 body 의 code 만 뽑는다. 파싱 실패는 무시 — 사유는 status 로 이미 갈렸다. */ | ||
| const readErrorCode = async (response: Response): Promise<string | null> => { | ||
| try { | ||
| const body = (await response.json()) as { code?: string | null }; | ||
| return body.code ?? null; | ||
| } catch { | ||
| return null; | ||
| } | ||
| }; | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| /** Share Extension에서 링크로 위시 등록 */ | ||
| export const postWishLinkFromShare = async ( | ||
| productUrl: string | ||
| ): Promise<PostWishLinkFromShareResultT> => { | ||
| if (!process.env.EXPO_PUBLIC_API_URL) | ||
| return { ok: false, message: 'API 주소가 설정되지 않았어요' }; | ||
| /** 빌드 설정 누락 — 다시 눌러도 결과가 같다 */ | ||
| if (!process.env.EXPO_PUBLIC_API_URL) return { ok: false, reason: 'server', retryable: false }; | ||
|
|
||
| const accessToken = await TokenStorage.getAccessToken(); | ||
| const refreshToken = await TokenStorage.getRefreshToken(); | ||
|
|
||
| if (!accessToken) return { ok: false, message: '로그인이 필요해요' }; | ||
| if (!accessToken) return { ok: false, reason: 'unauthenticated', retryable: false }; | ||
|
Comment on lines
56
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 토큰 저장소 예외를 실패 결과로 변환하세요. Line 56-57의 토큰 조회는 토큰 조회를 예외 처리 범위에 포함하세요. 저장소 오류를 적절한
🤖 Prompt for AI Agents |
||
|
|
||
| try { | ||
| /** 위시 등록 시도 */ | ||
|
|
@@ -38,7 +69,7 @@ export const postWishLinkFromShare = async ( | |
| if (!refreshResponse.ok) { | ||
| /** 죽은 토큰으로 재시도가 반복되지 않도록 정리 */ | ||
| if (refreshResponse.status === 401) await TokenStorage.clearTokens(); | ||
| return { ok: false, message: '로그인이 만료됐어요' }; | ||
| return { ok: false, reason: 'sessionExpired', retryable: false }; | ||
| } | ||
|
|
||
| /** 토큰 갱신 후 토큰 저장 */ | ||
|
|
@@ -51,10 +82,17 @@ export const postWishLinkFromShare = async ( | |
| postWishResponse = await postWishLink(productUrl, refreshBody.data.accessToken); | ||
| } | ||
|
|
||
| if (!postWishResponse.ok) return { ok: false, message: '요청 처리 중 오류가 발생했습니다.' }; | ||
| if (!postWishResponse.ok) { | ||
| const code = await readErrorCode(postWishResponse); | ||
| /** refreshToken 이 없어 갱신조차 못 한 401 도 세션 만료로 본다 */ | ||
| const reason: ShareFailureReasonT = | ||
| postWishResponse.status === 401 ? 'sessionExpired' : 'server'; | ||
|
|
||
| return { ok: false, reason, retryable: reason === 'server', ...(code ? { code } : {}) }; | ||
| } | ||
|
|
||
| return { ok: true }; | ||
| } catch { | ||
| return { ok: false, message: '네트워크 오류가 발생했어요' }; | ||
| return { ok: false, reason: 'network', retryable: true }; | ||
|
Comment on lines
95
to
+96
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 네트워크 예외와 내부 오류를 구분하세요. Line 95-96의 전송 오류만 이 경로에서 처리하세요. JSON 파싱 오류와 저장소 오류는 각각의 실패 사유와 재시도 정책으로 분류하세요. PR 목표의 실패 사유 구분 요구사항에 근거합니다. 🤖 Prompt for AI Agents |
||
| } | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
sessionExpired에도 로그인 유도 화면을 표시하세요.LOGIN_REQUIRED_REASONS는sessionExpired를 로그인 유도 사유로 분류합니다. 그러나 Line 114는unauthenticated만 로그인 유도 화면으로 처리합니다. 따라서 세션 만료는 일반 실패 제목과 오류 이미지를 표시합니다.isLoginPrompt조건에isLoginRequired를 사용하세요.수정 예시
Also applies to: 123-136
🤖 Prompt for AI Agents