-
Notifications
You must be signed in to change notification settings - Fork 3
feat: 백그라운드 스크립트 API 핸들러 리팩토링 및 추가 #89
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
base: main
Are you sure you want to change the base?
Conversation
Walkthrough핸들러 함수들을 별도의 모듈로 분리하여 각 메시지 타입에 따라 전담 함수가 비동기적으로 처리하도록 리팩토링했습니다. 메시지 리스너는 switch문으로 통합되어, 각 기능별 API 요청과 응답 처리를 전담 핸들러에 위임합니다. 새로운 핸들러와 index 모듈이 추가되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant ContentScript
participant Background
participant Handler
participant API
ContentScript->>Background: 메시지 전송 (예: FETCH_COSMETIC_DATA)
Background->>Handler: 해당 핸들러 함수 호출
Handler->>API: 외부 API로 POST 요청
API-->>Handler: 응답(JSON)
Handler->>Background: 결과 또는 에러 반환
Background->>ContentScript: 응답 메시지 전송
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/background/handlers/api/foodHandler.tsOops! Something went wrong! :( ESLint: 9.29.0 Error: The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it. src/background/handlers/api/imageAnalysisHandler.tsOops! Something went wrong! :( ESLint: 9.29.0 Error: The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it. src/background/handlers/api/cosmeticHandler.tsOops! Something went wrong! :( ESLint: 9.29.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: 11
♻️ Duplicate comments (1)
src/background/handlers/api/healthDataHandler.ts (1)
1-5: 타입 지정 및async문제 반복
any남용과 불필요한async키워드가 동일하게 나타납니다. 타입 정의 후return fetch(...)로 수정 권장.🧰 Tools
🪛 GitHub Check: lint-and-test
[warning] 4-4:
Unexpected any. Specify a different type
[warning] 3-3:
Unexpected any. Specify a different type
[warning] 2-2:
Unexpected any. Specify a different type
🧹 Nitpick comments (8)
src/background/handlers/api/imageAnalysisHandler.ts (1)
29-35: 로깅 일관성 부족
성공 시에는logger.debug를 사용하면서 실패 시console.error를 직접 호출하고 있습니다.logger.error로 통일해 주세요.src/background/handlers/api/cosmeticHandler.ts (2)
20-27: API 응답 형식 검사는 좋지만console.warn사용
이미logger유틸이 존재하므로logger.warn으로 교체하면 로깅 레벨 제어가 일관됩니다.
41-46: 에러 로깅 일관성
console.error대신logger.error로 통일해주세요.src/background/handlers/api/healthDataHandler.ts (1)
31-39: 에러 로깅 일관성
console.error→logger.error로 변경하여 로그 관리 일관성을 유지하세요.src/background/handlers/api/outlineInfoHandler.ts (2)
18-25: 상품 ID 정규식 하드코딩
/vp\/products\/(\d+)/패턴이 URL 변경에 매우 취약합니다. 최소한 정규식 실패 시 logger.warn 으로 기록하거나, 프론트엔드 라우팅 규칙을 공통 util 로 분리하세요.
45-56: 에러 로깅 일관성
console.error대신logger.error사용으로 통일 필요.src/background/handlers/api/reviewSummaryHandler.ts (1)
1-5:any타입 제거 권장
핸들러 인자에 모두any를 사용하면 타입 체크 이점이 사라집니다.
message→{ type: string; payload?: … },
sender→chrome.runtime.MessageSender,
sendResponse→(res: unknown) => void등으로 명시해주세요.src/background/handlers/api/foodHandler.ts (1)
16-18: URL 정규식 범용성 개선 제안
/vp\/products\/(\d+)/패턴은 경로 구조 변경에 매우 취약합니다.
new URL(activeTab.url).pathname.match(/(\d+)$/)처럼 뒤쪽에서 숫자 추출로 유연성을 높일 수 있습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
src/background/handlers/api/cosmeticHandler.ts(1 hunks)src/background/handlers/api/foodHandler.ts(1 hunks)src/background/handlers/api/healthDataHandler.ts(1 hunks)src/background/handlers/api/imageAnalysisHandler.ts(1 hunks)src/background/handlers/api/index.ts(1 hunks)src/background/handlers/api/outlineInfoHandler.ts(1 hunks)src/background/handlers/api/reviewSummaryHandler.ts(1 hunks)src/background/handlers/pageHandlers.ts(1 hunks)src/background/index.ts(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/background/handlers/api/imageAnalysisHandler.ts (2)
src/background/handlers/api/index.ts (1)
handleImageAnalysisFetch(2-2)src/utils/logger.ts (1)
logger(7-21)
🪛 GitHub Check: lint-and-test
src/background/handlers/api/cosmeticHandler.ts
[warning] 4-4:
Unexpected any. Specify a different type
[warning] 3-3:
Unexpected any. Specify a different type
[warning] 2-2:
Unexpected any. Specify a different type
src/background/handlers/api/healthDataHandler.ts
[warning] 4-4:
Unexpected any. Specify a different type
[warning] 3-3:
Unexpected any. Specify a different type
[warning] 2-2:
Unexpected any. Specify a different type
src/background/handlers/api/foodHandler.ts
[warning] 47-47:
'err' is defined but never used
[warning] 4-4:
Unexpected any. Specify a different type
[warning] 3-3:
Unexpected any. Specify a different type
[warning] 2-2:
Unexpected any. Specify a different type
🔇 Additional comments (1)
src/background/handlers/api/index.ts (1)
1-6: 모듈 재-export LGTM
핸들러들을 index 파일에서 재-export 하는 구조는 가독성과 tree-shaking 모두에 이점이 있습니다.
| fetch("https://voim.store/api/v1/image-analysis", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ url: imageUrl }), | ||
| }) | ||
| .then((res) => res.json()) |
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
res.ok 확인이 필요합니다
HTTP 오류 상태(4xx/5xx)에서도 res.json()을 시도하면 추가 오류가 발생할 수 있습니다. if (!res.ok) throw new Error(...) 패턴으로 처리하세요.
🤖 Prompt for AI Agents
In src/background/handlers/api/imageAnalysisHandler.ts around lines 10 to 15,
the fetch response is parsed to JSON without checking if the HTTP response
status is OK. To fix this, add a check for res.ok after receiving the response;
if it is false, throw an error with an appropriate message before calling
res.json(). This prevents attempting to parse error responses as JSON and avoids
additional errors.
| const imageUrl = message.payload?.url; | ||
|
|
||
| fetch("https://voim.store/api/v1/image-analysis", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ url: imageUrl }), | ||
| }) |
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.
imageUrl 유효성 검증 누락
message.payload?.url이 undefined 인 경우 그대로 API 호출이 이뤄집니다. 빈 URL 전송은 서버 400을 유발할 수 있으므로 검사 후 즉시 에러 응답을 보내는 것이 안전합니다.
🤖 Prompt for AI Agents
In src/background/handlers/api/imageAnalysisHandler.ts around lines 8 to 14, the
code assigns imageUrl from message.payload?.url without validating it, which can
cause an API call with an undefined or empty URL leading to server 400 errors.
Add a check after extracting imageUrl to verify it is defined and non-empty; if
not, immediately return or throw an error response instead of proceeding with
the fetch call.
| export const handleImageAnalysisFetch = async ( | ||
| message: any, | ||
| sender: any, | ||
| sendResponse: any, | ||
| ) => { |
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
타입 선언 및 async 키워드 사용이 부자연스럽습니다
message, sender, sendResponse를 모두 any로 두면 TypeScript의 장점을 잃습니다. 또한 함수에 async를 붙였지만 내부에서 await를 쓰지 않고 fetch(...).then() 체인을 반환하지 않아 Promise<void>가 아닌 undefined를 즉시 반환하게 됩니다.
-export const handleImageAnalysisFetch = async (
- message: any,
- sender: any,
- sendResponse: any,
-) => {
+interface ImageAnalysisMessage { payload: { url?: string } }
+export const handleImageAnalysisFetch = (
+ message: ImageAnalysisMessage,
+ sender: chrome.runtime.MessageSender,
+ sendResponse: (response: unknown) => void,
+) => {async 를 제거하고 마지막에 return fetch(...); 를 추가해 리스너에서 true 를 반환하지 않아도 런타임이 응답 대기를 인지하도록 하는 편이 명확합니다.
📝 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.
| export const handleImageAnalysisFetch = async ( | |
| message: any, | |
| sender: any, | |
| sendResponse: any, | |
| ) => { | |
| interface ImageAnalysisMessage { payload: { url?: string } } | |
| export const handleImageAnalysisFetch = ( | |
| message: ImageAnalysisMessage, | |
| sender: chrome.runtime.MessageSender, | |
| sendResponse: (response: unknown) => void, | |
| ) => { |
🤖 Prompt for AI Agents
In src/background/handlers/api/imageAnalysisHandler.ts around lines 3 to 7, the
parameters message, sender, and sendResponse are all typed as any, which loses
TypeScript's type safety benefits. Also, the function is marked async but does
not use await or return a Promise properly, causing it to return undefined
immediately. To fix this, remove the async keyword from the function
declaration, replace the any types with appropriate specific types for message,
sender, and sendResponse, and ensure the function returns the fetch call
directly by adding return before fetch(...). This will make the function return
a Promise and allow the runtime to recognize the asynchronous response
correctly.
| export const handleCosmeticDataFetch = async ( | ||
| message: any, | ||
| sender: any, | ||
| sendResponse: any, | ||
| ) => { | ||
| const { productId, html } = message.payload; |
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
타입 미지정 및 async 불필요
any 사용과 async 키워드 문제는 다른 핸들러와 동일합니다. 함수 본문이 await 를 사용하지 않으므로 async 제거 후 return fetch(...); 권장드립니다.
🧰 Tools
🪛 GitHub Check: lint-and-test
[warning] 4-4:
Unexpected any. Specify a different type
[warning] 3-3:
Unexpected any. Specify a different type
[warning] 2-2:
Unexpected any. Specify a different type
🤖 Prompt for AI Agents
In src/background/handlers/api/cosmeticHandler.ts lines 1 to 6, the function
handleCosmeticDataFetch uses 'any' types for parameters and is marked async
without using await. Remove the async keyword from the function declaration and
replace 'any' types with appropriate specific types matching other handlers.
Also, change the function body to return the fetch call directly without
awaiting it.
| fetch(`https://voim.store/api/v1/product-detail/${outline}`, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ html, productId }), | ||
| }) | ||
| .then((res) => res.json()) | ||
| .then((data) => { |
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
res.ok 체크 누락 및 예외 처리
다른 핸들러와 동일하게 HTTP 오류 상태에 대한 처리가 없습니다.
🤖 Prompt for AI Agents
In src/background/handlers/api/outlineInfoHandler.ts around lines 27 to 33, the
fetch response does not check res.ok to handle HTTP error statuses. Add a check
for res.ok after receiving the response, and if false, throw an error or handle
it appropriately to ensure HTTP errors are caught and managed consistently with
other handlers.
| sender: any, | ||
| sendResponse: any, | ||
| ) => { | ||
| const { productId, reviewRating, reviews } = message.payload; |
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.
message.payload 존재 여부 확인 필요
message.payload 가 undefined 이면 구조 분해 단계에서 런타임 오류가 발생합니다.
호출부 신뢰가 불확실하므로 if (!message?.payload) { … } 가드 코드를 추가해주세요.
🤖 Prompt for AI Agents
In src/background/handlers/api/reviewSummaryHandler.ts at line 6, the code
destructures properties from message.payload without checking if payload exists,
which can cause a runtime error if payload is undefined. Add a guard clause
before destructuring to check if message.payload is present, for example using
if (!message?.payload) { return or handle error }, to prevent runtime
exceptions.
| chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { | ||
| if (message.type === "PAGE_TYPE") { | ||
| // iframe으로 메시지 전달 | ||
| chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { | ||
| const activeTab = tabs[0]; | ||
| if (activeTab?.id) { | ||
| chrome.tabs.sendMessage( | ||
| activeTab.id, | ||
| { | ||
| type: "PAGE_TYPE", | ||
| value: message.value, | ||
| }, | ||
| (response) => { | ||
| sendResponse(response); | ||
| }, | ||
| ); | ||
| } | ||
| }); | ||
| return true; // 비동기 응답을 위해 true 반환 | ||
| } | ||
|
|
||
| if (message.type === "CART_PAGE") { | ||
| // iframe으로 메시지 전달 | ||
| chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { | ||
| const activeTab = tabs[0]; | ||
| if (activeTab?.id) { | ||
| chrome.tabs.sendMessage( | ||
| activeTab.id, | ||
| { | ||
| type: "CART_PAGE", | ||
| value: message.value, | ||
| }, | ||
| (response) => { | ||
| sendResponse(response); | ||
| }, | ||
| ); | ||
| } | ||
| }); | ||
| return true; // 비동기 응답을 위해 true 반환 | ||
| } | ||
|
|
||
| if (message.type === "CART_ITEMS_UPDATED") { | ||
| // 장바구니 아이템 정보를 저장 | ||
| chrome.storage.local.set({ cartItems: message.data }, () => { | ||
| // 현재 활성화된 탭에만 업데이트된 장바구니 정보 전달 | ||
| chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { | ||
| const activeTab = tabs[0]; | ||
| if (activeTab?.id) { | ||
| chrome.tabs | ||
| .sendMessage(activeTab.id, { | ||
| type: "CART_ITEMS_UPDATED", | ||
| data: message.data, | ||
| }) | ||
| .catch(() => { | ||
| // 메시지 전송 실패 시 무시 | ||
| }); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| // FOOD API | ||
| if (message.type === "FETCH_FOOD_DATA") { | ||
| chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { | ||
| const activeTab = tabs[0]; | ||
| if (!activeTab?.url) { | ||
| sendResponse({ | ||
| status: 400, | ||
| error: "상품 페이지를 찾을 수 없습니다.", | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| const productId = activeTab.url.match(/vp\/products\/(\d+)/)?.[1]; | ||
| if (!productId) { | ||
| sendResponse({ | ||
| status: 400, | ||
| error: "상품 ID를 찾을 수 없습니다.", | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| const payload = { | ||
| ...message.payload, | ||
| productId, | ||
| }; | ||
|
|
||
| fetch("https://voim.store/api/v1/products/foods", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify(payload), | ||
| }).then(async (res) => { | ||
| const text = await res.text(); | ||
|
|
||
| try { | ||
| const json = JSON.parse(text); | ||
| if (res.ok) { | ||
| sendResponse({ status: 200, data: json }); | ||
| } else { | ||
| sendResponse({ | ||
| status: res.status, | ||
| error: json?.message ?? "에러 발생", | ||
| }); | ||
| } | ||
| } catch (err) { | ||
| console.error("[voim] JSON 파싱 실패", text); | ||
| sendResponse({ | ||
| status: res.status, | ||
| error: "JSON 파싱 실패", | ||
| }); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| // IMAGE ANALYSIS API | ||
| if (message.type === "FETCH_IMAGE_ANALYSIS") { | ||
| const imageUrl = message.payload?.url; | ||
|
|
||
| fetch("https://voim.store/api/v1/image-analysis", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ url: imageUrl }), | ||
| }) | ||
| .then((res) => res.json()) | ||
| .then((data) => { | ||
| logger.debug("이미지 분석 API 응답:", data); | ||
| if (sender.tab?.id) { | ||
| chrome.tabs.sendMessage(sender.tab.id, { | ||
| type: "IMAGE_ANALYSIS_RESPONSE", | ||
| data: data.data, | ||
| }); | ||
| } | ||
| sendResponse({ | ||
| type: "IMAGE_ANALYSIS_RESPONSE", | ||
| data: data.data, | ||
| }); | ||
| }) | ||
| .catch((err) => { | ||
| console.error("이미지 분석 에러:", err); | ||
| if (sender.tab?.id) { | ||
| chrome.tabs.sendMessage(sender.tab.id, { | ||
| type: "IMAGE_ANALYSIS_ERROR", | ||
| error: err.message, | ||
| }); | ||
| } | ||
| sendResponse({ | ||
| type: "IMAGE_ANALYSIS_ERROR", | ||
| error: err.message, | ||
| }); | ||
| }); | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| // OUTLINE INFO API | ||
| if (message.type === "FETCH_OUTLINE_INFO") { | ||
| const { outline, html } = message.payload; | ||
|
|
||
| // 현재 활성화된 탭의 URL에서 productId 추출 | ||
| chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { | ||
| const activeTab = tabs[0]; | ||
| if (!activeTab?.url) { | ||
| sendResponse({ | ||
| type: "OUTLINE_INFO_ERROR", | ||
| error: "상품 페이지를 찾을 수 없습니다.", | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| const productId = activeTab.url.match(/vp\/products\/(\d+)/)?.[1]; | ||
| if (!productId) { | ||
| sendResponse({ | ||
| type: "OUTLINE_INFO_ERROR", | ||
| error: "상품 ID를 찾을 수 없습니다.", | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| fetch(`https://voim.store/api/v1/product-detail/${outline}`, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ html, productId }), | ||
| }) | ||
| .then((res) => res.json()) | ||
| .then((data) => { | ||
| if (sender.tab?.id) { | ||
| chrome.tabs.sendMessage(sender.tab.id, { | ||
| type: "OUTLINE_INFO_RESPONSE", | ||
| data: data.data, | ||
| }); | ||
| } | ||
| sendResponse({ | ||
| type: "OUTLINE_INFO_RESPONSE", | ||
| data: data.data, | ||
| }); | ||
| }) | ||
| .catch((err) => { | ||
| console.error("OUTLINE INFO 오류:", err); | ||
| if (sender.tab?.id) { | ||
| chrome.tabs.sendMessage(sender.tab.id, { | ||
| type: "OUTLINE_INFO_ERROR", | ||
| error: err.message, | ||
| }); | ||
| } | ||
| sendResponse({ | ||
| type: "OUTLINE_INFO_ERROR", | ||
| error: err.message, | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| // COSMETIC API | ||
| if (message.type === "FETCH_COSMETIC_DATA") { | ||
| const { productId, html } = message.payload; | ||
|
|
||
| fetch("https://voim.store/api/v1/cosmetic", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ productId, html }), | ||
| }) | ||
| .then(async (res) => { | ||
| const json = await res.json(); | ||
|
|
||
| return json; | ||
| }) | ||
| .then((data) => { | ||
| const raw = data?.data; | ||
|
|
||
| if (!raw || typeof raw !== "object") { | ||
| console.warn( | ||
| "[voim][background] data.data 형식 이상함:", | ||
| raw, | ||
| ); | ||
| sendResponse({ | ||
| type: "COSMETIC_DATA_ERROR", | ||
| error: "API 응답 형식 오류", | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| sendResponse({ | ||
| type: "COSMETIC_DATA_RESPONSE", | ||
| data: raw, | ||
| }); | ||
|
|
||
| if (sender.tab?.id) { | ||
| chrome.tabs.sendMessage(sender.tab.id, { | ||
| type: "COSMETIC_DATA_RESPONSE", | ||
| data: raw, | ||
| }); | ||
| } | ||
| }) | ||
| .catch((err) => { | ||
| console.error("[voim][background] COSMETIC 요청 실패:", err); | ||
| sendResponse({ | ||
| type: "COSMETIC_DATA_ERROR", | ||
| error: err.message, | ||
| }); | ||
|
|
||
| if (sender.tab?.id) { | ||
| chrome.tabs.sendMessage(sender.tab.id, { | ||
| type: "COSMETIC_DATA_ERROR", | ||
| error: err.message, | ||
| }); | ||
| } | ||
| }); | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| // // REVIEW SUMMARY API | ||
| if (message.type === "FETCH_REVIEW_SUMMARY") { | ||
| const { productId, reviewRating, reviews } = message.payload; | ||
|
|
||
| fetch("https://voim.store/api/v1/review/summary", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ productId, reviewRating, reviews }), | ||
| }) | ||
| .then(async (res) => { | ||
| if (!res.ok) { | ||
| const errorData = await res.json().catch(() => ({})); | ||
| throw new Error( | ||
| errorData.message || | ||
| `HTTP error! status: ${res.status}`, | ||
| ); | ||
| } | ||
| return res.json(); | ||
| }) | ||
| .then((data) => { | ||
| if (!data.data) { | ||
| throw new Error("서버 응답에 데이터가 없습니다."); | ||
| } | ||
|
|
||
| if (sender.tab?.id) { | ||
| chrome.tabs.sendMessage(sender.tab.id, { | ||
| type: "REVIEW_SUMMARY_RESPONSE", | ||
| data: data.data, | ||
| }); | ||
| } | ||
| sendResponse({ | ||
| type: "REVIEW_SUMMARY_RESPONSE", | ||
| data: data.data, | ||
| }); | ||
| }) | ||
| .catch((err) => { | ||
| console.error("[voim] REVIEW SUMMARY 오류:", err); | ||
| const errorMessage = | ||
| err.message || "리뷰 요약 처리 중 오류가 발생했습니다"; | ||
|
|
||
| if (sender.tab?.id) { | ||
| chrome.tabs.sendMessage(sender.tab.id, { | ||
| type: "REVIEW_SUMMARY_ERROR", | ||
| error: errorMessage, | ||
| }); | ||
| } | ||
| sendResponse({ | ||
| type: "REVIEW_SUMMARY_ERROR", | ||
| error: errorMessage, | ||
| }); | ||
| }); | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| // HEALTH DATA API | ||
| if (message.type === "FETCH_HEALTH_DATA") { | ||
| const { productId, title, html, birthYear, gender, allergies } = | ||
| message.payload; | ||
|
|
||
| fetch("https://voim.store/api/v1/health-food/keywords", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| productId, | ||
| title, | ||
| html, | ||
| birthYear, | ||
| gender, | ||
| allergies, | ||
| }), | ||
| }) | ||
| .then((res) => res.json()) | ||
| .then((data) => { | ||
| if (sender.tab?.id) { | ||
| chrome.tabs.sendMessage(sender.tab.id, { | ||
| type: "HEALTH_DATA_RESPONSE", | ||
| data: data.data, | ||
| }); | ||
| } | ||
| sendResponse({ type: "HEALTH_DATA_RESPONSE", data: data.data }); | ||
| }) | ||
| .catch((err) => { | ||
| console.error("HEALTH 요청 실패:", err); | ||
| if (sender.tab?.id) { | ||
| chrome.tabs.sendMessage(sender.tab.id, { | ||
| type: "HEALTH_DATA_ERROR", | ||
| error: err.message, | ||
| }); | ||
| } | ||
| sendResponse({ type: "HEALTH_DATA_ERROR", error: err.message }); | ||
| }); | ||
|
|
||
| return true; | ||
| } | ||
| }); | ||
|
|
||
| chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { | ||
| if (message.type === "FETCH_VENDOR_HTML") { | ||
| if (sender.tab?.id) { | ||
| chrome.tabs.sendMessage( | ||
| sender.tab.id, | ||
| { type: "GET_VENDOR_HTML" }, | ||
| (response) => { | ||
| sendResponse(response); | ||
| }, | ||
| ); | ||
| switch (message.type) { | ||
| case "PAGE_TYPE": | ||
| handlePageTypeMessage(message, sender, sendResponse); | ||
| return true; | ||
| case "CART_PAGE": | ||
| handleCartPageMessage(message, sender, sendResponse); | ||
| return true; | ||
| } | ||
| case "CART_ITEMS_UPDATED": | ||
| handleCartItemsUpdated(message, sender, sendResponse); | ||
| return true; | ||
| case "FETCH_FOOD_DATA": | ||
| handleFoodDataFetch(message, sender, sendResponse); | ||
| return true; | ||
| case "FETCH_IMAGE_ANALYSIS": | ||
| handleImageAnalysisFetch(message, sender, sendResponse); | ||
| return true; | ||
| case "FETCH_OUTLINE_INFO": | ||
| handleOutlineInfoFetch(message, sender, sendResponse); | ||
| return true; | ||
| case "FETCH_COSMETIC_DATA": | ||
| handleCosmeticDataFetch(message, sender, sendResponse); | ||
| return true; | ||
| case "FETCH_REVIEW_SUMMARY": | ||
| handleReviewSummaryFetch(message, sender, sendResponse); | ||
| return true; | ||
| case "FETCH_HEALTH_DATA": | ||
| handleHealthDataFetch(message, sender, sendResponse); | ||
| return true; | ||
| case "FETCH_VENDOR_HTML": | ||
| handleVendorHtmlFetch(message, sender, sendResponse); | ||
| return true; | ||
| case "GET_PRODUCT_TITLE": | ||
| handleProductTitleFetch(message, sender, sendResponse); | ||
| return true; | ||
| default: | ||
| return false; | ||
| } |
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
비동기 핸들러 오류 누락
각 case에서 handleXXXFetch() 호출 결과를 await 하거나 catch 로 감싸지 않으면 예외 발생 시 로그만 남고 sendResponse 가 호출되지 않아 호출 측에서 "The message port closed…" 오류가 나타납니다.
-case "FETCH_FOOD_DATA":
- handleFoodDataFetch(message, sender, sendResponse);
- return true;
+case "FETCH_FOOD_DATA":
+ Promise.resolve(handleFoodDataFetch(message, sender, sendResponse))
+ .catch(logger.error);
+ return true;다른 case 도 동일하게 래핑을 권장합니다.
📝 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.
| chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { | |
| if (message.type === "PAGE_TYPE") { | |
| // iframe으로 메시지 전달 | |
| chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { | |
| const activeTab = tabs[0]; | |
| if (activeTab?.id) { | |
| chrome.tabs.sendMessage( | |
| activeTab.id, | |
| { | |
| type: "PAGE_TYPE", | |
| value: message.value, | |
| }, | |
| (response) => { | |
| sendResponse(response); | |
| }, | |
| ); | |
| } | |
| }); | |
| return true; // 비동기 응답을 위해 true 반환 | |
| } | |
| if (message.type === "CART_PAGE") { | |
| // iframe으로 메시지 전달 | |
| chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { | |
| const activeTab = tabs[0]; | |
| if (activeTab?.id) { | |
| chrome.tabs.sendMessage( | |
| activeTab.id, | |
| { | |
| type: "CART_PAGE", | |
| value: message.value, | |
| }, | |
| (response) => { | |
| sendResponse(response); | |
| }, | |
| ); | |
| } | |
| }); | |
| return true; // 비동기 응답을 위해 true 반환 | |
| } | |
| if (message.type === "CART_ITEMS_UPDATED") { | |
| // 장바구니 아이템 정보를 저장 | |
| chrome.storage.local.set({ cartItems: message.data }, () => { | |
| // 현재 활성화된 탭에만 업데이트된 장바구니 정보 전달 | |
| chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { | |
| const activeTab = tabs[0]; | |
| if (activeTab?.id) { | |
| chrome.tabs | |
| .sendMessage(activeTab.id, { | |
| type: "CART_ITEMS_UPDATED", | |
| data: message.data, | |
| }) | |
| .catch(() => { | |
| // 메시지 전송 실패 시 무시 | |
| }); | |
| } | |
| }); | |
| }); | |
| } | |
| // FOOD API | |
| if (message.type === "FETCH_FOOD_DATA") { | |
| chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { | |
| const activeTab = tabs[0]; | |
| if (!activeTab?.url) { | |
| sendResponse({ | |
| status: 400, | |
| error: "상품 페이지를 찾을 수 없습니다.", | |
| }); | |
| return; | |
| } | |
| const productId = activeTab.url.match(/vp\/products\/(\d+)/)?.[1]; | |
| if (!productId) { | |
| sendResponse({ | |
| status: 400, | |
| error: "상품 ID를 찾을 수 없습니다.", | |
| }); | |
| return; | |
| } | |
| const payload = { | |
| ...message.payload, | |
| productId, | |
| }; | |
| fetch("https://voim.store/api/v1/products/foods", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }).then(async (res) => { | |
| const text = await res.text(); | |
| try { | |
| const json = JSON.parse(text); | |
| if (res.ok) { | |
| sendResponse({ status: 200, data: json }); | |
| } else { | |
| sendResponse({ | |
| status: res.status, | |
| error: json?.message ?? "에러 발생", | |
| }); | |
| } | |
| } catch (err) { | |
| console.error("[voim] JSON 파싱 실패", text); | |
| sendResponse({ | |
| status: res.status, | |
| error: "JSON 파싱 실패", | |
| }); | |
| } | |
| }); | |
| }); | |
| return true; | |
| } | |
| // IMAGE ANALYSIS API | |
| if (message.type === "FETCH_IMAGE_ANALYSIS") { | |
| const imageUrl = message.payload?.url; | |
| fetch("https://voim.store/api/v1/image-analysis", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ url: imageUrl }), | |
| }) | |
| .then((res) => res.json()) | |
| .then((data) => { | |
| logger.debug("이미지 분석 API 응답:", data); | |
| if (sender.tab?.id) { | |
| chrome.tabs.sendMessage(sender.tab.id, { | |
| type: "IMAGE_ANALYSIS_RESPONSE", | |
| data: data.data, | |
| }); | |
| } | |
| sendResponse({ | |
| type: "IMAGE_ANALYSIS_RESPONSE", | |
| data: data.data, | |
| }); | |
| }) | |
| .catch((err) => { | |
| console.error("이미지 분석 에러:", err); | |
| if (sender.tab?.id) { | |
| chrome.tabs.sendMessage(sender.tab.id, { | |
| type: "IMAGE_ANALYSIS_ERROR", | |
| error: err.message, | |
| }); | |
| } | |
| sendResponse({ | |
| type: "IMAGE_ANALYSIS_ERROR", | |
| error: err.message, | |
| }); | |
| }); | |
| return true; | |
| } | |
| // OUTLINE INFO API | |
| if (message.type === "FETCH_OUTLINE_INFO") { | |
| const { outline, html } = message.payload; | |
| // 현재 활성화된 탭의 URL에서 productId 추출 | |
| chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { | |
| const activeTab = tabs[0]; | |
| if (!activeTab?.url) { | |
| sendResponse({ | |
| type: "OUTLINE_INFO_ERROR", | |
| error: "상품 페이지를 찾을 수 없습니다.", | |
| }); | |
| return; | |
| } | |
| const productId = activeTab.url.match(/vp\/products\/(\d+)/)?.[1]; | |
| if (!productId) { | |
| sendResponse({ | |
| type: "OUTLINE_INFO_ERROR", | |
| error: "상품 ID를 찾을 수 없습니다.", | |
| }); | |
| return; | |
| } | |
| fetch(`https://voim.store/api/v1/product-detail/${outline}`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ html, productId }), | |
| }) | |
| .then((res) => res.json()) | |
| .then((data) => { | |
| if (sender.tab?.id) { | |
| chrome.tabs.sendMessage(sender.tab.id, { | |
| type: "OUTLINE_INFO_RESPONSE", | |
| data: data.data, | |
| }); | |
| } | |
| sendResponse({ | |
| type: "OUTLINE_INFO_RESPONSE", | |
| data: data.data, | |
| }); | |
| }) | |
| .catch((err) => { | |
| console.error("OUTLINE INFO 오류:", err); | |
| if (sender.tab?.id) { | |
| chrome.tabs.sendMessage(sender.tab.id, { | |
| type: "OUTLINE_INFO_ERROR", | |
| error: err.message, | |
| }); | |
| } | |
| sendResponse({ | |
| type: "OUTLINE_INFO_ERROR", | |
| error: err.message, | |
| }); | |
| }); | |
| }); | |
| return true; | |
| } | |
| // COSMETIC API | |
| if (message.type === "FETCH_COSMETIC_DATA") { | |
| const { productId, html } = message.payload; | |
| fetch("https://voim.store/api/v1/cosmetic", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ productId, html }), | |
| }) | |
| .then(async (res) => { | |
| const json = await res.json(); | |
| return json; | |
| }) | |
| .then((data) => { | |
| const raw = data?.data; | |
| if (!raw || typeof raw !== "object") { | |
| console.warn( | |
| "[voim][background] data.data 형식 이상함:", | |
| raw, | |
| ); | |
| sendResponse({ | |
| type: "COSMETIC_DATA_ERROR", | |
| error: "API 응답 형식 오류", | |
| }); | |
| return; | |
| } | |
| sendResponse({ | |
| type: "COSMETIC_DATA_RESPONSE", | |
| data: raw, | |
| }); | |
| if (sender.tab?.id) { | |
| chrome.tabs.sendMessage(sender.tab.id, { | |
| type: "COSMETIC_DATA_RESPONSE", | |
| data: raw, | |
| }); | |
| } | |
| }) | |
| .catch((err) => { | |
| console.error("[voim][background] COSMETIC 요청 실패:", err); | |
| sendResponse({ | |
| type: "COSMETIC_DATA_ERROR", | |
| error: err.message, | |
| }); | |
| if (sender.tab?.id) { | |
| chrome.tabs.sendMessage(sender.tab.id, { | |
| type: "COSMETIC_DATA_ERROR", | |
| error: err.message, | |
| }); | |
| } | |
| }); | |
| return true; | |
| } | |
| // // REVIEW SUMMARY API | |
| if (message.type === "FETCH_REVIEW_SUMMARY") { | |
| const { productId, reviewRating, reviews } = message.payload; | |
| fetch("https://voim.store/api/v1/review/summary", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ productId, reviewRating, reviews }), | |
| }) | |
| .then(async (res) => { | |
| if (!res.ok) { | |
| const errorData = await res.json().catch(() => ({})); | |
| throw new Error( | |
| errorData.message || | |
| `HTTP error! status: ${res.status}`, | |
| ); | |
| } | |
| return res.json(); | |
| }) | |
| .then((data) => { | |
| if (!data.data) { | |
| throw new Error("서버 응답에 데이터가 없습니다."); | |
| } | |
| if (sender.tab?.id) { | |
| chrome.tabs.sendMessage(sender.tab.id, { | |
| type: "REVIEW_SUMMARY_RESPONSE", | |
| data: data.data, | |
| }); | |
| } | |
| sendResponse({ | |
| type: "REVIEW_SUMMARY_RESPONSE", | |
| data: data.data, | |
| }); | |
| }) | |
| .catch((err) => { | |
| console.error("[voim] REVIEW SUMMARY 오류:", err); | |
| const errorMessage = | |
| err.message || "리뷰 요약 처리 중 오류가 발생했습니다"; | |
| if (sender.tab?.id) { | |
| chrome.tabs.sendMessage(sender.tab.id, { | |
| type: "REVIEW_SUMMARY_ERROR", | |
| error: errorMessage, | |
| }); | |
| } | |
| sendResponse({ | |
| type: "REVIEW_SUMMARY_ERROR", | |
| error: errorMessage, | |
| }); | |
| }); | |
| return true; | |
| } | |
| // HEALTH DATA API | |
| if (message.type === "FETCH_HEALTH_DATA") { | |
| const { productId, title, html, birthYear, gender, allergies } = | |
| message.payload; | |
| fetch("https://voim.store/api/v1/health-food/keywords", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| productId, | |
| title, | |
| html, | |
| birthYear, | |
| gender, | |
| allergies, | |
| }), | |
| }) | |
| .then((res) => res.json()) | |
| .then((data) => { | |
| if (sender.tab?.id) { | |
| chrome.tabs.sendMessage(sender.tab.id, { | |
| type: "HEALTH_DATA_RESPONSE", | |
| data: data.data, | |
| }); | |
| } | |
| sendResponse({ type: "HEALTH_DATA_RESPONSE", data: data.data }); | |
| }) | |
| .catch((err) => { | |
| console.error("HEALTH 요청 실패:", err); | |
| if (sender.tab?.id) { | |
| chrome.tabs.sendMessage(sender.tab.id, { | |
| type: "HEALTH_DATA_ERROR", | |
| error: err.message, | |
| }); | |
| } | |
| sendResponse({ type: "HEALTH_DATA_ERROR", error: err.message }); | |
| }); | |
| return true; | |
| } | |
| }); | |
| chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { | |
| if (message.type === "FETCH_VENDOR_HTML") { | |
| if (sender.tab?.id) { | |
| chrome.tabs.sendMessage( | |
| sender.tab.id, | |
| { type: "GET_VENDOR_HTML" }, | |
| (response) => { | |
| sendResponse(response); | |
| }, | |
| ); | |
| switch (message.type) { | |
| case "PAGE_TYPE": | |
| handlePageTypeMessage(message, sender, sendResponse); | |
| return true; | |
| case "CART_PAGE": | |
| handleCartPageMessage(message, sender, sendResponse); | |
| return true; | |
| } | |
| case "CART_ITEMS_UPDATED": | |
| handleCartItemsUpdated(message, sender, sendResponse); | |
| return true; | |
| case "FETCH_FOOD_DATA": | |
| handleFoodDataFetch(message, sender, sendResponse); | |
| return true; | |
| case "FETCH_IMAGE_ANALYSIS": | |
| handleImageAnalysisFetch(message, sender, sendResponse); | |
| return true; | |
| case "FETCH_OUTLINE_INFO": | |
| handleOutlineInfoFetch(message, sender, sendResponse); | |
| return true; | |
| case "FETCH_COSMETIC_DATA": | |
| handleCosmeticDataFetch(message, sender, sendResponse); | |
| return true; | |
| case "FETCH_REVIEW_SUMMARY": | |
| handleReviewSummaryFetch(message, sender, sendResponse); | |
| return true; | |
| case "FETCH_HEALTH_DATA": | |
| handleHealthDataFetch(message, sender, sendResponse); | |
| return true; | |
| case "FETCH_VENDOR_HTML": | |
| handleVendorHtmlFetch(message, sender, sendResponse); | |
| return true; | |
| case "GET_PRODUCT_TITLE": | |
| handleProductTitleFetch(message, sender, sendResponse); | |
| return true; | |
| default: | |
| return false; | |
| } | |
| chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { | |
| switch (message.type) { | |
| // … other cases … | |
| case "FETCH_FOOD_DATA": | |
| Promise.resolve(handleFoodDataFetch(message, sender, sendResponse)) | |
| .catch(logger.error); | |
| return true; | |
| // … other cases … | |
| default: | |
| return false; | |
| } | |
| }); |
🤖 Prompt for AI Agents
In src/background/index.ts between lines 70 and 107, the asynchronous handler
functions called in each case of the message listener are not awaited or wrapped
in try-catch blocks. This causes unhandled exceptions to prevent sendResponse
from being called, leading to "The message port closed…" errors on the caller
side. To fix this, update each case to await the corresponding handler call
inside an async function and wrap it with try-catch to handle errors properly
and ensure sendResponse is always called.
| fetch("https://voim.store/api/v1/products/foods", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify(payload), | ||
| }).then(async (res) => { | ||
| const text = await res.text(); | ||
|
|
||
| try { | ||
| const json = JSON.parse(text); | ||
| if (res.ok) { | ||
| sendResponse({ status: 200, data: json }); | ||
| } else { | ||
| sendResponse({ | ||
| status: res.status, | ||
| error: json?.message ?? "에러 발생", | ||
| }); | ||
| } | ||
| } catch (err) { | ||
| console.error("[voim] JSON 파싱 실패", text); | ||
| sendResponse({ | ||
| status: res.status, | ||
| error: "JSON 파싱 실패", | ||
| }); | ||
| } | ||
| }); |
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.
네트워크 오류 미처리로 응답 누락 위험
fetch() 실패(오프라인, CORS 등) 시 .then 체인이 실행되지 않아 sendResponse 가 호출되지 않습니다.
fetch("https://voim.store/api/v1/products/foods", { … })
.then(async (res) => { … })
+ .catch((error) => {
+ console.error("[voim] FOOD DATA 네트워크 오류:", error);
+ sendResponse({ status: 500, error: "네트워크 오류" });
+ });또한 catch 블록의 err 변수를 활용하도록 하거나 제거해 lint 경고를 해소하세요.
📝 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.
| fetch("https://voim.store/api/v1/products/foods", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }).then(async (res) => { | |
| const text = await res.text(); | |
| try { | |
| const json = JSON.parse(text); | |
| if (res.ok) { | |
| sendResponse({ status: 200, data: json }); | |
| } else { | |
| sendResponse({ | |
| status: res.status, | |
| error: json?.message ?? "에러 발생", | |
| }); | |
| } | |
| } catch (err) { | |
| console.error("[voim] JSON 파싱 실패", text); | |
| sendResponse({ | |
| status: res.status, | |
| error: "JSON 파싱 실패", | |
| }); | |
| } | |
| }); | |
| fetch("https://voim.store/api/v1/products/foods", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }).then(async (res) => { | |
| const text = await res.text(); | |
| try { | |
| const json = JSON.parse(text); | |
| if (res.ok) { | |
| sendResponse({ status: 200, data: json }); | |
| } else { | |
| sendResponse({ | |
| status: res.status, | |
| error: json?.message ?? "에러 발생", | |
| }); | |
| } | |
| } catch (err) { | |
| console.error("[voim] JSON 파싱 실패", text); | |
| sendResponse({ | |
| status: res.status, | |
| error: "JSON 파싱 실패", | |
| }); | |
| } | |
| }).catch((error) => { | |
| console.error("[voim] FOOD DATA 네트워크 오류:", error); | |
| sendResponse({ status: 500, error: "네트워크 오류" }); | |
| }); |
🧰 Tools
🪛 GitHub Check: lint-and-test
[warning] 47-47:
'err' is defined but never used
🤖 Prompt for AI Agents
In src/background/handlers/api/foodHandler.ts around lines 30 to 54, the fetch
call lacks a catch block to handle network errors, which can cause sendResponse
to never be called on failures like offline or CORS issues. Add a .catch block
after the .then chain to catch fetch errors and call sendResponse with an
appropriate error status and message. Also, update the existing catch block
inside .then to use the err parameter for logging or remove it if unused to fix
lint warnings.
| export const handleCartItemsUpdated = async ( | ||
| message: any, | ||
| sender: any, | ||
| sendResponse: any, | ||
| ) => { | ||
| chrome.storage.local.set({ cartItems: message.data }, () => { | ||
| chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { | ||
| const activeTab = tabs[0]; | ||
| if (activeTab?.id) { | ||
| chrome.tabs | ||
| .sendMessage(activeTab.id, { | ||
| type: "CART_ITEMS_UPDATED", | ||
| data: message.data, | ||
| }) | ||
| .catch(() => { | ||
| // 메시지 전송 실패 시 무시 | ||
| }); | ||
| } | ||
| }); | ||
| }); | ||
| }; |
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
handleCartItemsUpdated 가 응답을 반환하지 않음
sendResponse 가 한 번도 호출되지 않아 호출자가 영원히 대기합니다.
스토리지 저장 완료 후 최소 { ok: true } 정도의 응답을 반환하도록 수정하세요.
🤖 Prompt for AI Agents
In src/background/handlers/pageHandlers.ts around lines 45 to 65, the function
handleCartItemsUpdated does not call sendResponse, causing the caller to wait
indefinitely. Modify the function to call sendResponse with a response object
like { ok: true } after chrome.storage.local.set completes, ensuring the caller
receives a confirmation response.
| chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { | ||
| const activeTab = tabs[0]; | ||
| if (activeTab?.id) { | ||
| chrome.tabs.sendMessage( | ||
| activeTab.id, | ||
| { | ||
| type: "PAGE_TYPE", | ||
| value: message.value, | ||
| }, | ||
| (response) => { | ||
| sendResponse(response); | ||
| }, | ||
| ); | ||
| } | ||
| }); |
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.
activeTab 없을 때도 sendResponse 호출해야 함
탭이 없으면 핸들러가 silent-fail 하여 호출 측에서 포트 종료 오류가 발생합니다.
if (activeTab?.id) {
…
+} else {
+ sendResponse({ error: "활성 탭을 찾을 수 없습니다." });
}handlePageTypeMessage, handleCartPageMessage 모두 동일 패턴으로 보완해주세요.
📝 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.
| chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { | |
| const activeTab = tabs[0]; | |
| if (activeTab?.id) { | |
| chrome.tabs.sendMessage( | |
| activeTab.id, | |
| { | |
| type: "PAGE_TYPE", | |
| value: message.value, | |
| }, | |
| (response) => { | |
| sendResponse(response); | |
| }, | |
| ); | |
| } | |
| }); | |
| chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { | |
| const activeTab = tabs[0]; | |
| if (activeTab?.id) { | |
| chrome.tabs.sendMessage( | |
| activeTab.id, | |
| { | |
| type: "PAGE_TYPE", | |
| value: message.value, | |
| }, | |
| (response) => { | |
| sendResponse(response); | |
| }, | |
| ); | |
| } else { | |
| sendResponse({ error: "활성 탭을 찾을 수 없습니다." }); | |
| } | |
| }); |
🤖 Prompt for AI Agents
In src/background/handlers/pageHandlers.ts around lines 6 to 20, the current
code only calls sendResponse if activeTab exists, causing silent failure and
port closure errors when no active tab is found. Modify the code to ensure
sendResponse is called in all cases, including when activeTab is undefined or
null. Apply this fix to both handlePageTypeMessage and handleCartPageMessage
functions to prevent silent failures and properly close the communication port.
🧐 체크리스트
yarn build또는npm run build실행 후 정상적으로 동작하는지 확인했나요?yarn test)yarn lint및yarn prettify실행 후 문제가 없나요?feat:,fix:,chore:등 커밋 컨벤션을 따르고 있나요?README.md또는 관련 문서를 업데이트했나요?Summary by CodeRabbit
신규 기능
리팩터링