-
Notifications
You must be signed in to change notification settings - Fork 3
[Fix] sidebar 구현 #83
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
[Fix] sidebar 구현 #83
Conversation
|
""" Walkthrough이 PR은 식품, 화장품, 건강 제품 관련 React 컴포넌트의 데이터 흐름을 전면적으로 리팩토링하여, 더 이상 DOM 관찰자나 수동 렌더링을 사용하지 않고, 크롬 메시지 기반의 비동기 데이터 요청 및 응답 구조로 통일하였습니다. 또한, 백그라운드와 콘텐츠 스크립트의 메시지 핸들러, API 요청 함수, 타입 선언, UI 구조가 개선되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Sidebar
participant FoodComponent
participant Background
participant ContentScript
Sidebar->>FoodComponent: 렌더링 시 mount
FoodComponent->>ContentScript: "GET_VENDOR_HTML" 메시지 요청 (재시도 포함)
ContentScript->>FoodComponent: { html, productId }
FoodComponent->>Background: "FETCH_FOOD_DATA" 메시지 (payload 포함)
Background->>FoodComponent: API 응답 (allergyTypes, nutrients 등)
FoodComponent->>ContentScript: "GET_PRODUCT_TITLE" 메시지
ContentScript->>FoodComponent: { title }
FoodComponent->>FoodComponent: 상태 업데이트 및 UI 렌더링
sequenceDiagram
participant Sidebar
participant CosmeticComponent
participant Background
participant ContentScript
Sidebar->>CosmeticComponent: 렌더링 시 mount
CosmeticComponent->>ContentScript: "FETCH_VENDOR_HTML" 메시지 (재시도 포함)
ContentScript->>CosmeticComponent: { html, productId }
CosmeticComponent->>Background: "FETCH_COSMETIC_DATA" 메시지
Background->>CosmeticComponent: { detected ingredient keys }
CosmeticComponent->>CosmeticComponent: 상태 업데이트 및 UI 렌더링
Possibly related PRs
Poem
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
src/components/productComponents/infoComponent.tsxOops! Something went wrong! :( ESLint: 9.27.0 Error: The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it. src/components/productComponents/cosmeticComponent.tsxOops! Something went wrong! :( ESLint: 9.27.0 Error: The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it. src/components/sidebar/component.tsxOops! Something went wrong! :( ESLint: 9.27.0 Error: The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it. ✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
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.
Actionable comments posted: 6
🔭 Outside diff range comments (1)
src/tabs/myInfo/components/AllergySelectForm.tsx (1)
158-176: 💡 Verification agent🧩 Analysis chain
enum 변환 로직이 안전하게 구현되었습니다.
선택된 알레르기 이름들을 enum 값으로 변환하는 로직이 적절합니다.
filter(Boolean)을 사용하여 undefined 값을 제거하는 것도 좋은 방어 코드입니다.다른 컴포넌트에서 이 enum 타입을 올바르게 사용하는지 확인해주세요.
🏁 Script executed:
#!/bin/bash # Description: AllergyType enum 사용 현황을 확인합니다. # AllergyType 사용 위치 검색 rg -A 3 "AllergyType" --type tsx --type tsLength of output: 70
다른 컴포넌트에서
AllergyType사용 현황을 .ts 및 .tsx 파일에서 다시 검색해 보겠습니다.
🏁 Script executed:
#!/bin/bash # Description: 모든 .ts/.tsx 파일에서 AllergyType 사용 위치를 확인합니다. rg "AllergyType" -A 3 -g '*.ts' -g '*.tsx'Length of output: 3647
다른 컴포넌트의 AllergyType 사용 업데이트 필요
AllergyTypeenum 변환 로직은 적절하지만, 다른 컴포넌트에서 여전히 문자열 배열(string[])으로 처리되고 있어 enum 타입으로 변경이 필요합니다.
- src/components/productComponents/foodComponent.tsx
- useState 제네릭 타입을
string[] | null에서AllergyType[] | null로 변경- const [allergyTypes, setAllergyTypes] = useState<string[] | null>(null); + const [allergyTypes, setAllergyTypes] = useState<AllergyType[] | null>(null);- API 결과(
result.allergyTypes)를 enum 배열로 변환하는 로직 추가import { allergyNameToEnumMap, AllergyType } from 'src/tabs/myInfo/components/AllergySelectForm'; // … const enumList = (result.allergyTypes || []) .map(name => allergyNameToEnumMap[name]) .filter(Boolean) as AllergyType[]; setAllergyTypes(enumList);위 항목들을 반영해 enum 타입 일관성을 유지해주세요.
🧹 Nitpick comments (6)
src/components/sidebar/component.tsx (1)
112-114: 아키텍처 개선으로 코드가 단순해졌습니다.복잡한 DOM 관찰 로직을 제거하고 React 컴포넌트를 직접 렌더링하는 방식으로 변경한 것이 훌륭합니다. 이는 다음과 같은 이점을 제공합니다:
- 더 예측 가능한 렌더링
- 디버깅 용이성
- React의 생명주기와 일관된 동작
src/content/apiSetting/sendCosmeticDataRequest.tsx (1)
12-45: 견고한 에러 처리와 데이터 변환 로직입니다.함수가 다음과 같이 잘 구현되었습니다:
- Promise 기반의 현대적인 API
- 응답 형식 검증
- 적절한 에러 메시지
- 데이터 필터링 및 변환
작은 개선사항을 제안합니다.
export const sendCosmeticDataRequest = ( payload: CosmeticRequestPayload, ): Promise<string[]> => { + console.log('[sendCosmeticDataRequest] 요청 시작:', payload.productId); return new Promise((resolve, reject) => {디버깅을 위해 요청 시작 로그를 추가하는 것을 고려해보세요.
src/content/apiSetting/sendHealthDataRequest.tsx (1)
14-56: 로깅과 에러 처리가 잘 구현되었습니다.함수의 장점:
- 상세한 디버깅 로그
- 견고한 응답 검증
- 명확한 에러 메시지
일관성 개선을 위한 제안:
export const sendHealthDataRequest = ( payload: HealthRequestPayload, ): Promise<string[]> => { return new Promise((resolve, reject) => { - console.log("[voim] HEALTH API 요청 payload:", payload); + console.log("[sendHealthDataRequest] 요청 payload:", payload); chrome.runtime.sendMessage( { type: "FETCH_HEALTH_DATA", payload, }, (response: unknown) => { if (!response || typeof response !== "object") { console.error( - "[voim] HEALTH API 응답 형식 오류:", + "[sendHealthDataRequest] 응답 형식 오류:", response, );로그 접두사를 함수명과 일치시켜 일관성을 높이는 것을 고려해보세요.
src/components/productComponents/cosmeticComponent.tsx (1)
50-111: 재시도 로직이 잘 구현되었습니다. 유지보수성을 위한 개선을 고려해보세요.현재 구현은 잘 동작하지만, 재시도 설정을 상수로 추출하면 유지보수가 더 쉬워질 것입니다.
컴포넌트 상단에 다음 상수들을 추가하는 것을 고려해보세요:
const MAX_RETRY_ATTEMPTS = 10; const RETRY_INTERVAL_MS = 500;그리고 재시도 로직에서 이 상수들을 사용하세요:
-let retries = 10; +let retries = MAX_RETRY_ATTEMPTS;-}, 500); +}, RETRY_INTERVAL_MS);src/components/productComponents/foodComponent.tsx (1)
69-79: 제품 제목 가져오기 함수에 개선이 필요합니다.현재 함수에서
reject매개변수가 정의되었지만 사용되지 않습니다. 에러 처리를 일관성 있게 유지하는 것이 좋겠습니다.- const getProductTitle = (): Promise<string> => { - return new Promise((resolve, reject) => { + const getProductTitle = (): Promise<string> => { + return new Promise((resolve) => {src/components/productComponents/healthComponent.tsx (1)
170-170: 오타가 있습니다.
transperent는transparent의 오타입니다.- backgroundColor: "transperent", + backgroundColor: "transparent",
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
src/background/index.ts(3 hunks)src/components/productComponents/cosmeticComponent.tsx(3 hunks)src/components/productComponents/foodComponent.tsx(6 hunks)src/components/productComponents/healthComponent.tsx(1 hunks)src/components/sidebar/component.tsx(2 hunks)src/content/apiSetting/sendCosmeticDataRequest.tsx(1 hunks)src/content/apiSetting/sendFoodDataRequest.tsx(3 hunks)src/content/apiSetting/sendHealthDataRequest.tsx(1 hunks)src/content/coupang/categoryHandler/categoryHandlerCosmetic.tsx(0 hunks)src/content/coupang/categoryHandler/categoryHandlerFood.tsx(0 hunks)src/content/coupang/categoryHandler/categoryHandlerHealth.tsx(0 hunks)src/content/index.tsx(3 hunks)src/tabs/myInfo/components/AllergySelectForm.tsx(3 hunks)
💤 Files with no reviewable changes (3)
- src/content/coupang/categoryHandler/categoryHandlerHealth.tsx
- src/content/coupang/categoryHandler/categoryHandlerCosmetic.tsx
- src/content/coupang/categoryHandler/categoryHandlerFood.tsx
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/components/sidebar/component.tsx (2)
src/components/productComponents/foodComponent.tsx (1)
FoodComponent(46-296)src/components/productComponents/cosmeticComponent.tsx (1)
CosmeticComponent(28-176)
src/content/index.tsx (1)
src/content/coupang/categoryHandler/detectCategory.tsx (1)
detectCategoryType(1-32)
src/components/productComponents/cosmeticComponent.tsx (1)
src/content/apiSetting/sendCosmeticDataRequest.tsx (1)
sendCosmeticDataRequest(12-45)
🪛 GitHub Check: lint-and-test
src/background/index.ts
[warning] 161-161:
'err' is defined but never used
[warning] 150-150:
Unexpected console statement. Only these console methods are allowed: error, warn
[warning] 149-149:
Unexpected console statement. Only these console methods are allowed: error, warn
[warning] 285-285:
Unexpected console statement. Only these console methods are allowed: error, warn
🔇 Additional comments (25)
src/tabs/myInfo/components/AllergySelectForm.tsx (2)
10-30: 타입 안전성 개선을 위한 좋은 변경입니다!
AllergyTypeenum 도입은 문자열 기반 알레르기 관리에서 타입 안전성을 크게 향상시킵니다. 각 알레르기 항목에 대한 주석도 이해도를 높여줍니다.
38-58: 한국어-영어 매핑이 잘 구현되었습니다.
allergyNameToEnumMap객체를 통한 한국어 이름과 enum 값 간의 매핑이 명확하고 완전합니다. 모든 알레르기 항목이 올바르게 매핑되어 있습니다.src/components/sidebar/component.tsx (1)
8-9: 새로운 컴포넌트 import가 깔끔합니다.
FoodComponent와CosmeticComponent를 직접 import하여 사용하는 방식이 명확하고 직관적입니다.src/content/apiSetting/sendCosmeticDataRequest.tsx (1)
1-11: 타입 정의가 명확하고 완전합니다.
CosmeticRequestPayload와CosmeticAPIResponse인터페이스가 잘 정의되어 있어 타입 안전성을 보장합니다.src/content/apiSetting/sendHealthDataRequest.tsx (1)
1-13: 타입 정의가 완전하고 명확합니다.
HealthRequestPayload와HealthAPIResponse인터페이스가 필요한 모든 필드를 포함하고 있어 좋습니다. 특히 사용자 정보(birthYear, gender, allergies)까지 포함한 것이 적절합니다.src/content/index.tsx (3)
92-115: 잘 구현된 유틸리티 함수입니다!DOM 요소를 비동기적으로 기다리는 함수가 타임아웃 처리와 MutationObserver 정리를 포함하여 깔끔하게 구현되었습니다.
116-125: 제품 제목 추출 리스너가 잘 구현되었습니다!적절한 null 처리와 디버그 로깅이 포함된 깔끔한 구현입니다.
126-153: 비동기 처리와 보안이 개선되었습니다!
waitForEl을 사용한 비동기 요소 대기와onerror속성 제거를 통한 XSS 방지가 잘 구현되었습니다.src/components/productComponents/cosmeticComponent.tsx (1)
146-173: UI 단순화가 잘 이루어졌습니다!모든 감지된 성분을 하나의 섹션에 표시하는 것이 사용자 경험을 개선합니다.
src/background/index.ts (1)
452-482: 제품 제목 가져오기 핸들러가 잘 구현되었습니다!적절한 에러 처리와 탭 통신이 포함된 견고한 구현입니다.
src/components/productComponents/foodComponent.tsx (6)
24-44: 알레르기 매핑 상수 추가는 좋은 개선사항입니다.코드의 가독성과 유지보수성을 향상시키는 좋은 변경사항입니다. 한국어 라벨 매핑이 명확하게 정의되어 있습니다.
84-96: 스토리지에서 데이터 가져오기 로직이 개선되었습니다.새로운
Allergies필드 추가와 디버그 로깅이 개발 및 디버깅에 도움이 될 것입니다.
152-169: API 요청 payload 구성이 올바릅니다.새로운 API 구조에 맞게 payload가 올바르게 구성되었습니다. 로깅 추가도 디버깅에 도움이 됩니다.
223-253: 조건부 렌더링 로직이 개선되었습니다.
nutrientAlerts.length > 0조건을 추가하여 빈 배열일 때 불필요한 렌더링을 방지하는 것은 좋은 개선사항입니다.
267-293: 알레르기 섹션 렌더링이 올바르게 구현되었습니다.새로운
allergyNameMap을 사용한 매핑과 조건부 렌더링이 적절하게 구현되었습니다.
167-168:Details
❌ Incorrect review comment
API 응답 구조 변경에 대한 검증이 필요합니다.
코드에서
result.overRecommendationNutrients에 직접 접근하고 있지만, AI 요약에 따르면 이 데이터가nutrientResponse객체 안에 중첩되어 있다고 합니다. 실제 API 응답 구조와 일치하는지 확인이 필요합니다.다음 스크립트를 실행하여
sendFoodDataRequest함수의 반환 타입을 확인해보세요:
🏁 Script executed:
#!/bin/bash # sendFoodDataRequest 함수의 반환 타입과 응답 구조 확인 ast-grep --pattern 'export const sendFoodDataRequest = ($$$): Promise<$RETURN_TYPE> => { $$$ }'Length of output: 6119
API 응답 구조 확인 결과, 컴포넌트 접근 방식이 올바릅니다.
sendFoodDataRequest함수가 이미overRecommendationNutrients와allergyTypes를 최상위 프로퍼티로 반환하므로,setNutrientAlerts(result.overRecommendationNutrients || []); setAllergyTypes(result.allergyTypes || []);현재 코드 그대로 사용하시면 됩니다. 별도의 수정이 필요 없습니다.
Likely an incorrect or invalid review comment.
src/content/apiSetting/sendFoodDataRequest.tsx (5)
20-24: API 응답 구조 변경이 올바르게 반영되었습니다.
overRecommendationNutrients가nutrientResponse객체 안으로 중첩된 새로운 구조가 명확하게 정의되었습니다.
28-31: 함수 반환 타입 단순화가 적절합니다.복잡한 중첩된 타입 대신 명시적인 객체 타입으로 반환 타입을 정의한 것이 가독성을 향상시킵니다.
52-65: 데이터 추출 로직이 올바르게 업데이트되었습니다.새로운 중첩된 구조에서 데이터를 올바르게 추출하고 있으며, 변수명도
actual에서data로 더 명확하게 변경되었습니다.
74-76: 알레르기 검증 로직 중복 제거가 효율적입니다.이미
Array.isArray(data.allergyTypes)검사를 통해 배열임이 확인되었으므로, 중복된 배열 검사를 제거한 것이 적절합니다.
79-83: 반환 객체 구조가 명확합니다.명시적인 객체 구조로 반환하여 호출하는 쪽에서 데이터에 접근하기 쉽게 만들었습니다.
src/components/productComponents/healthComponent.tsx (4)
4-37: 건강 효능 매핑 상수가 포괄적으로 정의되었습니다.다양한 건강 효능에 대한 한국어 매핑이 잘 정의되어 있어 사용자 경험을 향상시킬 것입니다.
57-67: getProductTitle 함수가 일관성 있게 구현되었습니다.foodComponent와 동일한 패턴으로 구현되어 코드 일관성을 유지하고 있습니다.
176-194: 건강 효능 렌더링 로직이 적절합니다.각 건강 효능을 개별 항목으로 렌더링하고 매핑된 한국어 이름을 사용하는 것이 좋습니다.
130-132:Details
✅ Verification successful
sendHealthDataRequest 함수 호출을 확인해야 합니다.
sendHealthDataRequest함수가 실제로 존재하고 올바른 타입을 반환하는지 확인이 필요합니다.다음 스크립트로 함수 존재 여부를 확인해보세요:
🏁 Script executed:
#!/bin/bash # sendHealthDataRequest 함수 존재 확인 fd "sendHealthDataRequest" --type f --extension tsx --extension ts | head -5Length of output: 125
다음 스크립트를 실행하여
sendHealthDataRequest함수의 시그니처와 반환 타입을 확인해주세요:
🏁 Script executed:
#!/bin/bash # sendHealthDataRequest 함수 정의 및 반환 타입 확인 head -n50 src/content/apiSetting/sendHealthDataRequest.tsxLength of output: 1589
sendHealthDataRequest 함수 존재 및 반환 타입 확인 완료
src/content/apiSetting/sendHealthDataRequest.tsx에export const sendHealthDataRequest(payload: HealthRequestPayload): Promise<string[]>로 정의되어 있습니다.- 반환 타입이
Promise<string[]>로, 호출부의result는 항상string[]이므로 타입에 문제가 없습니다.
| return; | ||
| } | ||
|
|
||
| const category = await detectCategoryType(); |
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.
불필요한 await 사용을 제거하세요.
detectCategoryType() 함수는 동기 함수이므로 await 키워드가 필요하지 않습니다.
다음과 같이 수정하세요:
- const category = await detectCategoryType();
+ const category = detectCategoryType();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const category = await detectCategoryType(); | |
| const category = detectCategoryType(); |
🤖 Prompt for AI Agents
In src/content/index.tsx at line 58, remove the unnecessary await keyword before
the call to detectCategoryType() since it is a synchronous function. Change the
line to directly assign the result of detectCategoryType() to the category
variable without using await.
| status: res.status, | ||
| error: json?.message ?? "에러 발생", | ||
| }); |
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.
console.log 대신 logger를 사용하세요.
프로덕션 환경을 위해 기존 logger 유틸리티를 사용하는 것이 좋습니다.
다음과 같이 수정하세요:
-console.log("[voim] 응답 상태 코드:", res.status);
-console.log("[voim] 응답 원문:", text);
+logger.debug("[voim] 응답 상태 코드:", res.status);
+logger.debug("[voim] 응답 원문:", text);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| console.log("[voim] 응답 상태 코드:", res.status); | |
| console.log("[voim] 응답 원문:", text); | |
| try { | |
| logger.debug("[voim] 응답 상태 코드:", res.status); | |
| logger.debug("[voim] 응답 원문:", text); | |
| try { |
🧰 Tools
🪛 GitHub Check: lint-and-test
[warning] 150-150:
Unexpected console statement. Only these console methods are allowed: error, warn
[warning] 149-149:
Unexpected console statement. Only these console methods are allowed: error, warn
🤖 Prompt for AI Agents
In src/background/index.ts around lines 148 to 150, replace the console.log
statements with calls to the existing logger utility to ensure consistent and
configurable logging in production. Identify the appropriate logger method
(e.g., logger.info or logger.debug) and use it to log the response status code
and response text instead of console.log.
| } | ||
| sendResponse({ status: 500, error: err.message }); | ||
| }); | ||
| }); |
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.
catch 블록에서 err 변수를 사용하세요.
에러 정보를 로그에 포함시켜 디버깅을 용이하게 하세요.
다음과 같이 수정하세요:
-console.error("[voim] JSON 파싱 실패", text);
+console.error("[voim] JSON 파싱 실패", text, err);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| console.error("[voim] JSON 파싱 실패", text); | |
| console.error("[voim] JSON 파싱 실패", text, err); |
🧰 Tools
🪛 GitHub Check: lint-and-test
[warning] 161-161:
'err' is defined but never used
🤖 Prompt for AI Agents
In src/background/index.ts at line 161, the catch block logs a generic error
message without including the caught error details. Modify the console.error
call to include the caught error variable (commonly named err) alongside the
message and text to provide detailed error information for easier debugging.
| if (!raw || typeof raw !== "object") { | ||
| console.warn( | ||
| "[voim][background] data.data 형식 이상함:", | ||
| raw, |
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.
console.log 대신 logger를 사용하세요.
일관성을 위해 logger 유틸리티를 사용하세요.
다음과 같이 수정하세요:
-console.log("[voim][background] 응답 원문:", json);
+logger.debug("[voim][background] 응답 원문:", json);Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 GitHub Check: lint-and-test
[warning] 285-285:
Unexpected console statement. Only these console methods are allowed: error, warn
🤖 Prompt for AI Agents
In src/background/index.ts at line 285, replace any console.log statements with
the logger utility to maintain consistency in logging. Identify where
console.log is used around this line and change it to use the logger's
appropriate logging method instead.
| const response = await new Promise<{ | ||
| html: string; | ||
| productId: string; | ||
| }>((resolve, reject) => { | ||
| chrome.runtime.sendMessage( | ||
| { type: "FETCH_VENDOR_HTML" }, | ||
| (res) => { | ||
| if ( | ||
| chrome.runtime.lastError || | ||
| !res?.html || | ||
| !res?.productId || | ||
| res.html.trim() === "" | ||
| ) { | ||
| console.warn( | ||
| "[voim] FETCH_VENDOR_HTML 응답 없음, 대기 중...", | ||
| ); | ||
| let retries = 10; | ||
| const interval = setInterval(() => { | ||
| chrome.runtime.sendMessage( | ||
| { type: "FETCH_VENDOR_HTML" }, | ||
| (retryRes) => { | ||
| if ( | ||
| retryRes?.html?.trim() && | ||
| retryRes?.productId | ||
| ) { | ||
| console.log( | ||
| "[voim] FETCH_VENDOR_HTML 재시도 성공:", | ||
| retryRes, | ||
| ); | ||
| clearInterval(interval); | ||
| resolve(retryRes); | ||
| } else if (--retries === 0) { | ||
| clearInterval(interval); | ||
| reject( | ||
| new Error( | ||
| "HTML 또는 productId 누락", | ||
| ), | ||
| ); | ||
| } | ||
| }, | ||
| ); | ||
| }, 500); | ||
| } else { | ||
| console.log( | ||
| "[voim] FETCH_VENDOR_HTML 성공 응답:", | ||
| res, | ||
| ); | ||
| resolve(res); | ||
| } | ||
| }, | ||
| ); | ||
| }); |
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.
재시도 로직의 잠재적 메모리 누수 문제가 있습니다.
재시도 로직에서 chrome.runtime.lastError 확인 없이 setInterval이 실행될 수 있으며, 컴포넌트가 언마운트된 후에도 interval이 계속 실행될 수 있습니다.
const interval = setInterval(() => {
chrome.runtime.sendMessage(
{ type: "FETCH_VENDOR_HTML" },
(retryRes) => {
+ if (chrome.runtime.lastError) {
+ console.warn("[voim] 재시도 중 런타임 에러:", chrome.runtime.lastError);
+ if (--retries === 0) {
+ clearInterval(interval);
+ reject(new Error("런타임 에러로 인한 재시도 실패"));
+ }
+ return;
+ }
if (
retryRes?.html?.trim() &&
retryRes?.productId또한 컴포넌트 언마운트 시 interval 정리를 위한 cleanup 함수를 추가하는 것을 고려해보세요.
🤖 Prompt for AI Agents
In src/components/productComponents/foodComponent.tsx between lines 99 and 150,
the retry logic using setInterval lacks checks for chrome.runtime.lastError and
does not clear the interval if the component unmounts, causing potential memory
leaks. To fix this, add a check for chrome.runtime.lastError inside the retry
callback to avoid unnecessary retries, and implement a cleanup function in the
component to clear the interval when the component unmounts, ensuring the
interval does not continue running after unmount.
| const response = await new Promise<{ | ||
| html: string; | ||
| productId: string; | ||
| }>((resolve, reject) => { | ||
| chrome.runtime.sendMessage( | ||
| { type: "FETCH_VENDOR_HTML" }, | ||
| (res) => { | ||
| if (!res?.html?.trim() || !res?.productId) { | ||
| let retries = 10; | ||
| const interval = setInterval(() => { | ||
| chrome.runtime.sendMessage( | ||
| { type: "FETCH_VENDOR_HTML" }, | ||
| (retryRes) => { | ||
| if ( | ||
| retryRes?.html?.trim() && | ||
| retryRes?.productId | ||
| ) { | ||
| clearInterval(interval); | ||
| resolve(retryRes); | ||
| } else if (--retries === 0) { | ||
| clearInterval(interval); | ||
| reject( | ||
| new Error( | ||
| "HTML 또는 productId 누락", | ||
| ), | ||
| ); | ||
| } | ||
| }, | ||
| ); | ||
| }, 500); | ||
| } else { | ||
| resolve(res); | ||
| } | ||
| }, | ||
| ); | ||
| }); |
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.
재시도 로직에서 동일한 메모리 누수 위험이 있습니다.
foodComponent와 동일한 문제가 있습니다. chrome.runtime.lastError 확인과 컴포넌트 언마운트 시 cleanup이 필요합니다.
const interval = setInterval(() => {
chrome.runtime.sendMessage(
{ type: "FETCH_VENDOR_HTML" },
(retryRes) => {
+ if (chrome.runtime.lastError) {
+ console.warn("[voim][HealthComponent] 재시도 중 런타임 에러:", chrome.runtime.lastError);
+ if (--retries === 0) {
+ clearInterval(interval);
+ reject(new Error("런타임 에러로 인한 재시도 실패"));
+ }
+ return;
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const response = await new Promise<{ | |
| html: string; | |
| productId: string; | |
| }>((resolve, reject) => { | |
| chrome.runtime.sendMessage( | |
| { type: "FETCH_VENDOR_HTML" }, | |
| (res) => { | |
| if (!res?.html?.trim() || !res?.productId) { | |
| let retries = 10; | |
| const interval = setInterval(() => { | |
| chrome.runtime.sendMessage( | |
| { type: "FETCH_VENDOR_HTML" }, | |
| (retryRes) => { | |
| if ( | |
| retryRes?.html?.trim() && | |
| retryRes?.productId | |
| ) { | |
| clearInterval(interval); | |
| resolve(retryRes); | |
| } else if (--retries === 0) { | |
| clearInterval(interval); | |
| reject( | |
| new Error( | |
| "HTML 또는 productId 누락", | |
| ), | |
| ); | |
| } | |
| }, | |
| ); | |
| }, 500); | |
| } else { | |
| resolve(res); | |
| } | |
| }, | |
| ); | |
| }); | |
| const response = await new Promise<{ | |
| html: string; | |
| productId: string; | |
| }>((resolve, reject) => { | |
| chrome.runtime.sendMessage( | |
| { type: "FETCH_VENDOR_HTML" }, | |
| (res) => { | |
| if (!res?.html?.trim() || !res?.productId) { | |
| let retries = 10; | |
| const interval = setInterval(() => { | |
| chrome.runtime.sendMessage( | |
| { type: "FETCH_VENDOR_HTML" }, | |
| (retryRes) => { | |
| // Check for runtime errors before inspecting retryRes | |
| if (chrome.runtime.lastError) { | |
| console.warn( | |
| "[voim][HealthComponent] 재시도 중 런타임 에러:", | |
| chrome.runtime.lastError, | |
| ); | |
| if (--retries === 0) { | |
| clearInterval(interval); | |
| reject( | |
| new Error( | |
| "런타임 에러로 인한 재시도 실패", | |
| ), | |
| ); | |
| } | |
| return; | |
| } | |
| if ( | |
| retryRes?.html?.trim() && | |
| retryRes?.productId | |
| ) { | |
| clearInterval(interval); | |
| resolve(retryRes); | |
| } else if (--retries === 0) { | |
| clearInterval(interval); | |
| reject( | |
| new Error( | |
| "HTML 또는 productId 누락", | |
| ), | |
| ); | |
| } | |
| }, | |
| ); | |
| }, 500); | |
| } else { | |
| resolve(res); | |
| } | |
| }, | |
| ); | |
| }); |
🤖 Prompt for AI Agents
In src/components/productComponents/healthComponent.tsx around lines 82 to 117,
the retry logic using setInterval for chrome.runtime.sendMessage lacks cleanup
on component unmount and does not check chrome.runtime.lastError, risking memory
leaks. Fix this by adding a cleanup function to clear the interval when the
component unmounts and by checking chrome.runtime.lastError after each
sendMessage call to handle errors properly and avoid unresolved promises.
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.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/components/productComponents/healthComponent.tsx (1)
92-112:⚠️ Potential issue재시도 로직의 메모리 누수 위험이 여전히 존재합니다.
이전 리뷰에서 지적된 문제가 아직 해결되지 않았습니다.
chrome.runtime.lastError확인과 컴포넌트 언마운트 시 cleanup이 필요합니다.다음 diff를 적용하여 문제를 해결하세요:
+ let intervalId: NodeJS.Timeout; const interval = setInterval(() => { chrome.runtime.sendMessage( { type: "FETCH_VENDOR_HTML" }, (retryRes) => { + if (chrome.runtime.lastError) { + console.warn("[voim][HealthComponent] 재시도 중 런타임 에러:", chrome.runtime.lastError); + if (--retries === 0) { + clearInterval(interval); + reject(new Error("런타임 에러로 인한 재시도 실패")); + } + return; + } if ( retryRes?.html?.trim() && retryRes?.productId ) { clearInterval(interval); resolve(retryRes); } else if (--retries === 0) { clearInterval(interval); reject( new Error( "HTML 또는 productId 누락", ), ); } }, ); }, 500); + intervalId = interval;또한 컴포넌트 언마운트 시 interval을 정리하기 위해 useEffect cleanup 함수를 추가해야 합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/components/productComponents/cosmeticComponent.tsx(3 hunks)src/components/productComponents/foodComponent.tsx(7 hunks)src/components/productComponents/healthComponent.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/components/productComponents/foodComponent.tsx
- src/components/productComponents/cosmeticComponent.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/productComponents/healthComponent.tsx (2)
src/content/apiSetting/sendHealthDataRequest.tsx (1)
sendHealthDataRequest(14-56)src/components/Loading/component.tsx (1)
Loading(5-14)
🔇 Additional comments (5)
src/components/productComponents/healthComponent.tsx (5)
2-3: 새로운 의존성 추가가 적절합니다.
sendHealthDataRequest함수와Loading컴포넌트를 올바르게 import하였습니다.
5-38: 건강 효능 매핑 딕셔너리가 잘 구현되었습니다.한국어 라벨 매핑을 위한
healthEffectMap이 포괄적이고 명확하게 정의되었습니다.
58-68: getProductTitle 함수의 에러 처리가 올바릅니다.
chrome.runtime.lastError체크와 적절한 fallback 처리가 구현되어 있습니다.
154-166: 로딩 UI 구현이 개선되었습니다.중앙 정렬된
Loading컴포넌트와 명확한 로딩 텍스트가 사용자 경험을 향상시킵니다.
191-209: 건강 효능 목록 렌더링이 잘 구현되었습니다.
healthEffectMap을 활용한 한국어 라벨 표시와 적절한 스타일링이 적용되었습니다.
| const fetchData = async () => { | ||
| try { | ||
| const { birthYear, gender, Allergies } = | ||
| await chrome.storage.local.get([ | ||
| "birthYear", | ||
| "gender", | ||
| "Allergies", | ||
| ]); | ||
|
|
||
| const { birthYear, gender } = await chrome.storage.local.get([ | ||
| "birthYear", | ||
| "gender", | ||
| ]); | ||
| if (!birthYear || !gender) return; | ||
|
|
||
| const rawHtml = targetEl.outerHTML | ||
| .replace(/\sonerror=\"[^\"]*\"/g, "") | ||
| .replace(/\n/g, "") | ||
| .trim(); | ||
| const title = await getProductTitle(); | ||
| const response = await new Promise<{ | ||
| html: string; | ||
| productId: string; | ||
| }>((resolve, reject) => { | ||
| chrome.runtime.sendMessage( | ||
| { type: "FETCH_VENDOR_HTML" }, | ||
| (res) => { | ||
| if (!res?.html?.trim() || !res?.productId) { | ||
| let retries = 10; | ||
| const interval = setInterval(() => { | ||
| chrome.runtime.sendMessage( | ||
| { type: "FETCH_VENDOR_HTML" }, | ||
| (retryRes) => { | ||
| if ( | ||
| retryRes?.html?.trim() && | ||
| retryRes?.productId | ||
| ) { | ||
| clearInterval(interval); | ||
| resolve(retryRes); | ||
| } else if (--retries === 0) { | ||
| clearInterval(interval); | ||
| reject( | ||
| new Error( | ||
| "HTML 또는 productId 누락", | ||
| ), | ||
| ); | ||
| } | ||
| }, | ||
| ); | ||
| }, 500); | ||
| } else { | ||
| resolve(res); | ||
| } | ||
| }, | ||
| ); | ||
| }); | ||
|
|
||
| chrome.runtime.sendMessage( | ||
| { | ||
| type: "FETCH_HEALTH_DATA", | ||
| payload: { | ||
| productId, | ||
| title: document.title, | ||
| html: rawHtml, | ||
| birthYear: Number(birthYear), | ||
| gender: gender?.toUpperCase() || "UNKNOWN", | ||
| allergies: [], | ||
| }, | ||
| }, | ||
| (res) => { | ||
| const data = res?.data?.types || []; | ||
| setHealthEffects(data); | ||
| if (res?.data?.types) { | ||
| setHealthEffects(res.data.types); | ||
| } | ||
| }, | ||
| ); | ||
| }; | ||
| const payload = { | ||
| productId: response.productId, | ||
| title, | ||
| html: response.html, | ||
| birthYear: Number(birthYear), | ||
| gender: gender.toUpperCase(), | ||
| allergies: Allergies || [], | ||
| }; | ||
|
|
||
| const targetEl = | ||
| document.querySelector(".vendor-item") || | ||
| document.querySelector(".product-detail-content") || | ||
| document.querySelector(".prod-image"); | ||
| console.log("[voim] HEALTH API 요청 payload:", payload); | ||
|
|
||
| if (targetEl) { | ||
| fetchData(targetEl); | ||
| } else { | ||
| const observer = new MutationObserver(() => { | ||
| const el = | ||
| document.querySelector(".vendor-item") || | ||
| document.querySelector(".product-detail-content") || | ||
| document.querySelector(".prod-image"); | ||
| if (el) { | ||
| observer.disconnect(); | ||
| fetchData(el); | ||
| } | ||
| }); | ||
| observer.observe(document.body, { childList: true, subtree: true }); | ||
| return () => observer.disconnect(); | ||
| } | ||
| const result = await sendHealthDataRequest(payload); | ||
| console.log("[voim] HEALTH API 응답:", result); | ||
| setHealthTypes(result || []); | ||
| } catch (e) { | ||
| console.error("[voim] HEALTH API 실패:", e); | ||
| } | ||
| }; | ||
|
|
||
| fetchData(); | ||
| }, []); |
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.
🛠️ Refactor suggestion
비동기 데이터 페칭 로직에 컴포넌트 언마운트 처리가 필요합니다.
현재 구현은 컴포넌트가 언마운트된 후에도 상태 업데이트를 시도할 수 있습니다.
useRef를 사용하여 컴포넌트 마운트 상태를 추적하고 cleanup 함수를 추가하세요:
+import React, { useEffect, useState, useRef } from "react";
export const HealthComponent = () => {
const [healthTypes, setHealthTypes] = useState<string[] | null>(null);
+ const isMountedRef = useRef(true);
useEffect(() => {
+ isMountedRef.current = true;
const fetchData = async () => {
try {
// ... existing logic ...
- setHealthTypes(result || []);
+ if (isMountedRef.current) {
+ setHealthTypes(result || []);
+ }
} catch (e) {
console.error("[voim] HEALTH API 실패:", e);
}
};
fetchData();
+
+ return () => {
+ isMountedRef.current = false;
+ };
}, []);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const fetchData = async () => { | |
| try { | |
| const { birthYear, gender, Allergies } = | |
| await chrome.storage.local.get([ | |
| "birthYear", | |
| "gender", | |
| "Allergies", | |
| ]); | |
| const { birthYear, gender } = await chrome.storage.local.get([ | |
| "birthYear", | |
| "gender", | |
| ]); | |
| if (!birthYear || !gender) return; | |
| const rawHtml = targetEl.outerHTML | |
| .replace(/\sonerror=\"[^\"]*\"/g, "") | |
| .replace(/\n/g, "") | |
| .trim(); | |
| const title = await getProductTitle(); | |
| const response = await new Promise<{ | |
| html: string; | |
| productId: string; | |
| }>((resolve, reject) => { | |
| chrome.runtime.sendMessage( | |
| { type: "FETCH_VENDOR_HTML" }, | |
| (res) => { | |
| if (!res?.html?.trim() || !res?.productId) { | |
| let retries = 10; | |
| const interval = setInterval(() => { | |
| chrome.runtime.sendMessage( | |
| { type: "FETCH_VENDOR_HTML" }, | |
| (retryRes) => { | |
| if ( | |
| retryRes?.html?.trim() && | |
| retryRes?.productId | |
| ) { | |
| clearInterval(interval); | |
| resolve(retryRes); | |
| } else if (--retries === 0) { | |
| clearInterval(interval); | |
| reject( | |
| new Error( | |
| "HTML 또는 productId 누락", | |
| ), | |
| ); | |
| } | |
| }, | |
| ); | |
| }, 500); | |
| } else { | |
| resolve(res); | |
| } | |
| }, | |
| ); | |
| }); | |
| chrome.runtime.sendMessage( | |
| { | |
| type: "FETCH_HEALTH_DATA", | |
| payload: { | |
| productId, | |
| title: document.title, | |
| html: rawHtml, | |
| birthYear: Number(birthYear), | |
| gender: gender?.toUpperCase() || "UNKNOWN", | |
| allergies: [], | |
| }, | |
| }, | |
| (res) => { | |
| const data = res?.data?.types || []; | |
| setHealthEffects(data); | |
| if (res?.data?.types) { | |
| setHealthEffects(res.data.types); | |
| } | |
| }, | |
| ); | |
| }; | |
| const payload = { | |
| productId: response.productId, | |
| title, | |
| html: response.html, | |
| birthYear: Number(birthYear), | |
| gender: gender.toUpperCase(), | |
| allergies: Allergies || [], | |
| }; | |
| const targetEl = | |
| document.querySelector(".vendor-item") || | |
| document.querySelector(".product-detail-content") || | |
| document.querySelector(".prod-image"); | |
| console.log("[voim] HEALTH API 요청 payload:", payload); | |
| if (targetEl) { | |
| fetchData(targetEl); | |
| } else { | |
| const observer = new MutationObserver(() => { | |
| const el = | |
| document.querySelector(".vendor-item") || | |
| document.querySelector(".product-detail-content") || | |
| document.querySelector(".prod-image"); | |
| if (el) { | |
| observer.disconnect(); | |
| fetchData(el); | |
| } | |
| }); | |
| observer.observe(document.body, { childList: true, subtree: true }); | |
| return () => observer.disconnect(); | |
| } | |
| const result = await sendHealthDataRequest(payload); | |
| console.log("[voim] HEALTH API 응답:", result); | |
| setHealthTypes(result || []); | |
| } catch (e) { | |
| console.error("[voim] HEALTH API 실패:", e); | |
| } | |
| }; | |
| fetchData(); | |
| }, []); | |
| import React, { useEffect, useState, useRef } from "react"; | |
| export const HealthComponent = () => { | |
| const [healthTypes, setHealthTypes] = useState<string[] | null>(null); | |
| const isMountedRef = useRef(true); | |
| useEffect(() => { | |
| isMountedRef.current = true; | |
| const fetchData = async () => { | |
| try { | |
| const { birthYear, gender, Allergies } = | |
| await chrome.storage.local.get([ | |
| "birthYear", | |
| "gender", | |
| "Allergies", | |
| ]); | |
| if (!birthYear || !gender) return; | |
| const title = await getProductTitle(); | |
| const response = await new Promise<{ | |
| html: string; | |
| productId: string; | |
| }>((resolve, reject) => { | |
| chrome.runtime.sendMessage( | |
| { type: "FETCH_VENDOR_HTML" }, | |
| (res) => { | |
| if (!res?.html?.trim() || !res?.productId) { | |
| let retries = 10; | |
| const interval = setInterval(() => { | |
| chrome.runtime.sendMessage( | |
| { type: "FETCH_VENDOR_HTML" }, | |
| (retryRes) => { | |
| if ( | |
| retryRes?.html?.trim() && | |
| retryRes?.productId | |
| ) { | |
| clearInterval(interval); | |
| resolve(retryRes); | |
| } else if (--retries === 0) { | |
| clearInterval(interval); | |
| reject( | |
| new Error( | |
| "HTML 또는 productId 누락", | |
| ), | |
| ); | |
| } | |
| }, | |
| ); | |
| }, 500); | |
| } else { | |
| resolve(res); | |
| } | |
| }, | |
| ); | |
| }); | |
| const payload = { | |
| productId: response.productId, | |
| title, | |
| html: response.html, | |
| birthYear: Number(birthYear), | |
| gender: gender.toUpperCase(), | |
| allergies: Allergies || [], | |
| }; | |
| console.log("[voim] HEALTH API 요청 payload:", payload); | |
| const result = await sendHealthDataRequest(payload); | |
| console.log("[voim] HEALTH API 응답:", result); | |
| if (isMountedRef.current) { | |
| setHealthTypes(result || []); | |
| } | |
| } catch (e) { | |
| console.error("[voim] HEALTH API 실패:", e); | |
| } | |
| }; | |
| fetchData(); | |
| return () => { | |
| isMountedRef.current = false; | |
| }; | |
| }, []); | |
| // ...rendering logic... | |
| }; |
🤖 Prompt for AI Agents
In src/components/productComponents/healthComponent.tsx around lines 71 to 140,
the asynchronous fetchData function updates state after awaiting async calls
without checking if the component is still mounted, which can cause errors if
the component unmounts during the fetch. Fix this by using a useRef hook to
track the mounted state of the component, setting it to true on mount and false
in a cleanup function inside useEffect, and conditionally update state only if
the component is still mounted.
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.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/components/imageCheck/imageModal.tsx (2)
3-3: 사용하지 않는 import를 제거해주세요.
Playerimport가 더 이상 사용되지 않는 것 같습니다.Loading컴포넌트가 Lottie 애니메이션을 처리하므로 이 import를 제거할 수 있습니다.-import { Player } from "@lottiefiles/react-lottie-player";
90-116: 로딩 UI 리팩토링이 잘 구현되었습니다.새로운 Loading 컴포넌트를 사용한 로딩 UI 구조가 깔끔하고 일관성 있게 구현되었습니다. flexbox를 이용한 중앙 정렬과 고정 크기 컨테이너가 적절합니다.
다만 하나의 개선사항을 제안드립니다:
<div style={{ padding: "16px", display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "center", height: "320px", }} > - <div - style={{ width: "260px", height: "243px" }} - > - <Loading /> - </div> + <Loading /> <p style={{ marginTop: "8px", fontFamily: "KoddiUD OnGothic", fontSize: "24px", fontWeight: 600, color: "#555", }} > 이미지를 분석 중입니다. 잠시만 기다려주세요. </p> </div>Loading 컴포넌트가 이미 고정 크기(260x243)를 가지고 있으므로, 추가적인 래퍼 div가 불필요할 수 있습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/components/imageCheck/controlImage.tsx(1 hunks)src/components/imageCheck/imageModal.tsx(2 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/components/imageCheck/controlImage.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/imageCheck/imageModal.tsx (1)
src/components/Loading/component.tsx (1)
Loading(5-14)
🔇 Additional comments (1)
src/components/imageCheck/imageModal.tsx (1)
4-4: 새로운 Loading 컴포넌트 import가 잘 적용되었습니다.UI 일관성을 위한 Loading 컴포넌트 도입이 적절합니다.
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.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/components/productComponents/cosmeticComponent.tsx(3 hunks)src/components/productComponents/infoComponent.tsx(3 hunks)src/components/sidebar/component.tsx(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/components/sidebar/component.tsx
- src/components/productComponents/cosmeticComponent.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/productComponents/infoComponent.tsx (1)
src/contexts/ThemeContext.tsx (1)
useTheme(96-102)
🪛 Biome (1.9.4)
src/components/productComponents/infoComponent.tsx
[error] 107-107: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🔇 Additional comments (6)
src/components/productComponents/infoComponent.tsx (6)
9-11: 인터페이스 정의가 깔끔합니다.
InfoComponentProps인터페이스가 잘 정의되어 있고,categoryType의 타입이 명확하게 지정되어 있습니다.
13-15: 컴포넌트 시그니처 개선이 좋습니다.매개변수가 없던 컴포넌트에서 타입이 지정된 프롭스를 받는 함수형 컴포넌트로 개선되었습니다.
24-30: 동적 라벨링 로직이 잘 구현되어 있습니다.
categoryType에 따라 메인 라벨을 동적으로 설정하는 로직이 명확하고 효율적입니다. 음식/건강 제품과 기타 제품을 구분하여 적절한 라벨을 제공합니다.
42-42: 메시지 전송 로직이 간소화되어 좋습니다.Chrome 런타임 메시지 전송이 더 간결하게 작성되었습니다.
95-101: 문자열 처리 로직이 효율적입니다.문자열을 줄별로 분할하고, 앞의 하이픈을 제거하며, 빈 줄을 필터링하는 체이닝 로직이 깔끔하게 구현되어 있습니다.
77-77: 동적 렌더링으로 개선되었습니다.정적 배열 대신 동적으로 생성된
outlineCategories배열을 사용하여 렌더링하는 방식이 더 유연합니다.
| {info | ||
| .split("\n") | ||
| .map((item) => | ||
| item.replace(/^-/, "").trim(), | ||
| ) | ||
| .filter(Boolean) | ||
| .map((item, index) => ( | ||
| <li | ||
| key={index} | ||
| className="leading-relaxed" | ||
| dangerouslySetInnerHTML={{ | ||
| __html: item.replace( | ||
| /<strong>(.*?)<\/strong>/g, | ||
| `<strong style="color: #8914FF;">$1</strong>`, | ||
| ), | ||
| }} | ||
| /> | ||
| ))} |
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.
보안 취약점: XSS 공격 위험성이 있습니다.
dangerouslySetInnerHTML을 사용하여 사용자 콘텐츠를 렌더링하는 것은 XSS 공격에 취약합니다. 외부에서 가져온 HTML 콘텐츠를 직접 주입하면 악성 스크립트가 실행될 수 있습니다.
다음과 같이 안전한 방식으로 수정하는 것을 권장합니다:
- <li
- key={index}
- className="leading-relaxed"
- dangerouslySetInnerHTML={{
- __html: item.replace(
- /<strong>(.*?)<\/strong>/g,
- `<strong style="color: #8914FF;">$1</strong>`,
- ),
- }}
- />
+ <li key={index} className="leading-relaxed">
+ {item.split(/<strong>(.*?)<\/strong>/g).map((part, partIndex) => {
+ if (partIndex % 2 === 1) {
+ return (
+ <strong
+ key={partIndex}
+ style={{ color: "#8914FF" }}
+ >
+ {part}
+ </strong>
+ );
+ }
+ return part;
+ })}
+ </li>또는 DOMPurify 같은 HTML 새니타이제이션 라이브러리를 사용하세요.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {info | |
| .split("\n") | |
| .map((item) => | |
| item.replace(/^-/, "").trim(), | |
| ) | |
| .filter(Boolean) | |
| .map((item, index) => ( | |
| <li | |
| key={index} | |
| className="leading-relaxed" | |
| dangerouslySetInnerHTML={{ | |
| __html: item.replace( | |
| /<strong>(.*?)<\/strong>/g, | |
| `<strong style="color: #8914FF;">$1</strong>`, | |
| ), | |
| }} | |
| /> | |
| ))} | |
| {info | |
| .split("\n") | |
| .map((item) => | |
| item.replace(/^-/, "").trim(), | |
| ) | |
| .filter(Boolean) | |
| .map((item, index) => ( | |
| <li key={index} className="leading-relaxed"> | |
| {item | |
| .split(/<strong>(.*?)<\/strong>/g) | |
| .map((part, partIndex) => { | |
| if (partIndex % 2 === 1) { | |
| return ( | |
| <strong | |
| key={partIndex} | |
| style={{ color: "#8914FF" }} | |
| > | |
| {part} | |
| </strong> | |
| ); | |
| } | |
| return part; | |
| })} | |
| </li> | |
| ))} |
🧰 Tools
🪛 Biome (1.9.4)
[error] 107-107: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🤖 Prompt for AI Agents
In src/components/productComponents/infoComponent.tsx around lines 95 to 112,
the use of dangerouslySetInnerHTML to render user content poses an XSS security
risk. To fix this, avoid using dangerouslySetInnerHTML with raw HTML strings.
Instead, sanitize the HTML content using a library like DOMPurify before
rendering, or refactor the code to render plain text or React elements safely
without injecting HTML directly.
| { key: "MAIN", label: mainLabel }, | ||
| { key: "USAGE", label: "사용 방법 및 대상" }, | ||
| { key: "WARNING", label: "주의 및 보관" }, | ||
| { key: "SPECS", label: "구성 및 디자인" }, | ||
| ] as const; | ||
|
|
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.
💡 Verification agent
🧩 Analysis chain
카테고리 구조 개선이 필요할 수 있습니다.
카테고리 배열이 동적으로 생성되어 좋지만, "CERTIFICATION" 카테고리가 제거되었습니다. 이 변경이 의도적인지 확인이 필요합니다.
다음 스크립트를 실행하여 CERTIFICATION 카테고리 사용 여부를 확인해주세요:
🏁 Script executed:
#!/bin/bash
# Description: CERTIFICATION 카테고리가 다른 곳에서 사용되는지 확인
# Expected: CERTIFICATION 관련 참조가 없어야 함
echo "=== CERTIFICATION 카테고리 사용 검색 ==="
rg -i "certification" --type typescript --type tsxLength of output: 160
rg 명령어에서 --type typescript를 인식하지 못해 검색이 실행되지 않았습니다. 파일 확장자를 glob 패턴으로 지정하여 다시 확인해보겠습니다.
🏁 Script executed:
#!/bin/bash
echo "=== CERTIFICATION 카테고리 검색 재시도 ==="
rg -i certification -g '*.ts' -g '*.tsx'Length of output: 365
CERTIFICATION 카테고리 재추가 필요
infoComponent.tsx에서 categories 배열에 “CERTIFICATION” 항목이 빠졌으나, 다음 위치들에서는 여전히 해당 키를 참조하고 있습니다:
- src/components/productComponents/infoComponent.tsx (Lines 32–37):
outlineCategories - src/components/productComponents/infoComponent.tsx:
type OutlineCategory = "... | "CERTIFICATION"; - src/content/apiSetting/sendInfoRequest.tsx:
outline: "MAIN" | ... | "CERTIFICATION",
위치한 곳들이 일치하도록, 의도대로 “CERTIFICATION” 카테고리를 다음과 같이 다시 추가하거나 제거 의도를 명확히 검토해주세요.
const outlineCategories = [
{ key: "MAIN", label: mainLabel },
{ key: "USAGE", label: "사용 방법 및 대상" },
{ key: "WARNING", label: "주의 및 보관" },
{ key: "SPECS", label: "구성 및 디자인" },
+ { key: "CERTIFICATION", label: "인증 및 허가" },
] as const;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const outlineCategories = [ | |
| { key: "MAIN", label: mainLabel }, | |
| { key: "USAGE", label: "사용 방법 및 대상" }, | |
| { key: "WARNING", label: "주의 및 보관" }, | |
| { key: "SPECS", label: "구성 및 디자인" }, | |
| ] as const; | |
| const outlineCategories = [ | |
| { key: "MAIN", label: mainLabel }, | |
| { key: "USAGE", label: "사용 방법 및 대상" }, | |
| { key: "WARNING", label: "주의 및 보관" }, | |
| { key: "SPECS", label: "구성 및 디자인" }, | |
| { key: "CERTIFICATION", label: "인증 및 허가" }, | |
| ] as const; |
🤖 Prompt for AI Agents
In src/components/productComponents/infoComponent.tsx around lines 32 to 37, the
"CERTIFICATION" category was removed from the outlineCategories array but is
still referenced in the type OutlineCategory and other files like
sendInfoRequest.tsx. To fix this, re-add the "CERTIFICATION" category object
with the appropriate label back into the outlineCategories array to keep the
category definitions consistent across the codebase, or confirm and propagate
its removal everywhere if the deletion was intentional.
🧐 체크리스트
yarn build또는npm run build실행 후 정상적으로 동작하는지 확인했나요?yarn test)yarn lint및yarn prettify실행 후 문제가 없나요?feat:,fix:,chore:등 커밋 컨벤션을 따르고 있나요?README.md또는 관련 문서를 업데이트했나요?Summary by CodeRabbit
신규 기능
기능 개선
버그 수정
리팩터
기타