Skip to content

Conversation

@choihooo
Copy link
Contributor

@choihooo choihooo commented Jun 16, 2025

🧐 체크리스트

  • 코드 실행 확인: yarn build 또는 npm run build 실행 후 정상적으로 동작하는지 확인했나요?
  • 테스트 통과 여부: Jest 테스트를 실행했고 모든 테스트가 통과했나요? (yarn test)
  • ESLint & Prettier 검사: yarn lintyarn prettify 실행 후 문제가 없나요?
  • PR 제목 확인: PR 제목이 feat:, fix:, chore: 등 커밋 컨벤션을 따르고 있나요?
  • 변경 사항 설명: PR 설명에 변경된 내용을 충분히 작성했나요?
  • 리뷰어가 이해하기 쉽게 작성: 코드 리뷰어가 쉽게 이해할 수 있도록 가독성 높은 코드를 작성했나요?
  • 문서 업데이트 필요 여부: 기능 추가/변경 사항이 있다면 README.md 또는 관련 문서를 업데이트했나요?

Summary by CodeRabbit

  • 신규 기능

    • 식품, 화장품, 건강 데이터, 이미지 분석, 제품 개요, 리뷰 요약 등 다양한 데이터 요청 및 응답 기능이 추가되었습니다.
    • 장바구니 및 페이지 관련 메시지 처리 기능이 도입되었습니다.
  • 리팩터링

    • 메시지 처리 구조가 통합되어, 다양한 요청을 효율적으로 분리된 핸들러에서 관리하도록 개선되었습니다.

@coderabbitai
Copy link

coderabbitai bot commented Jun 16, 2025

Walkthrough

핸들러 함수들을 별도의 모듈로 분리하여 각 메시지 타입에 따라 전담 함수가 비동기적으로 처리하도록 리팩토링했습니다. 메시지 리스너는 switch문으로 통합되어, 각 기능별 API 요청과 응답 처리를 전담 핸들러에 위임합니다. 새로운 핸들러와 index 모듈이 추가되었습니다.

Changes

파일/경로 요약 변경 내용 요약
src/background/handlers/api/cosmeticHandler.ts
src/background/handlers/api/foodHandler.ts
src/background/handlers/api/healthDataHandler.ts
src/background/handlers/api/imageAnalysisHandler.ts
src/background/handlers/api/outlineInfoHandler.ts
src/background/handlers/api/reviewSummaryHandler.ts
각 기능별로 비동기 핸들러 함수(handleCosmeticDataFetch, handleFoodDataFetch, handleHealthDataFetch, handleImageAnalysisFetch, handleOutlineInfoFetch, handleReviewSummaryFetch) 신설 및 export
src/background/handlers/api/index.ts 여러 핸들러 함수들을 한 곳에서 재-export하는 index 모듈 신설
src/background/handlers/pageHandlers.ts 페이지/장바구니 관련 메시지 핸들러 함수들(handlePageTypeMessage 등) 신설 및 export
src/background/index.ts 메시지 리스너를 switch문으로 통합, 각 메시지 타입별로 핸들러 함수에 위임하도록 리팩토링

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: 응답 메시지 전송
Loading

Possibly related PRs

Poem

🐇
새로운 핸들러 hop-hop 등장,
메시지마다 함수가 깡총깡총!
switch문 아래 질서가 생기고,
API 요청도 척척 맡겨요.
모듈화된 코드에 당근 한 입,
토끼는 오늘도 기쁘게 뛰어요!

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

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/background/handlers/api/foodHandler.ts

Oops! Something went wrong! :(

ESLint: 9.29.0

Error: The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it.
at /node_modules/eslint/lib/config/config-loader.js:145:10
at async loadTypeScriptConfigFileWithJiti (/node_modules/eslint/lib/config/config-loader.js:144:3)
at async loadConfigFile (/node_modules/eslint/lib/config/config-loader.js:266:11)
at async ConfigLoader.calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:589:23)
at async #calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:770:23)
at async /node_modules/eslint/lib/eslint/eslint.js:760:6
at async Promise.all (index 0)
at async ESLint.lintFiles (/node_modules/eslint/lib/eslint/eslint.js:757:19)
at async Object.execute (/node_modules/eslint/lib/cli.js:632:14)
at async main (/node_modules/eslint/bin/eslint.js:175:19)

src/background/handlers/api/imageAnalysisHandler.ts

Oops! Something went wrong! :(

ESLint: 9.29.0

Error: The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it.
at /node_modules/eslint/lib/config/config-loader.js:145:10
at async loadTypeScriptConfigFileWithJiti (/node_modules/eslint/lib/config/config-loader.js:144:3)
at async loadConfigFile (/node_modules/eslint/lib/config/config-loader.js:266:11)
at async ConfigLoader.calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:589:23)
at async #calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:770:23)
at async /node_modules/eslint/lib/eslint/eslint.js:760:6
at async Promise.all (index 0)
at async ESLint.lintFiles (/node_modules/eslint/lib/eslint/eslint.js:757:19)
at async Object.execute (/node_modules/eslint/lib/cli.js:632:14)
at async main (/node_modules/eslint/bin/eslint.js:175:19)

src/background/handlers/api/cosmeticHandler.ts

Oops! Something went wrong! :(

ESLint: 9.29.0

Error: The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it.
at /node_modules/eslint/lib/config/config-loader.js:145:10
at async loadTypeScriptConfigFileWithJiti (/node_modules/eslint/lib/config/config-loader.js:144:3)
at async loadConfigFile (/node_modules/eslint/lib/config/config-loader.js:266:11)
at async ConfigLoader.calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:589:23)
at async #calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:770:23)
at async /node_modules/eslint/lib/eslint/eslint.js:760:6
at async Promise.all (index 0)
at async ESLint.lintFiles (/node_modules/eslint/lib/eslint/eslint.js:757:19)
at async Object.execute (/node_modules/eslint/lib/cli.js:632:14)
at async main (/node_modules/eslint/bin/eslint.js:175:19)

  • 6 others
✨ Finishing Touches
  • 📝 Generate Docstrings

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a 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.errorlogger.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?: … },
senderchrome.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

📥 Commits

Reviewing files that changed from the base of the PR and between 43747a8 and 8e1f4ca.

📒 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 모두에 이점이 있습니다.

Comment on lines +10 to +15
fetch("https://voim.store/api/v1/image-analysis", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url: imageUrl }),
})
.then((res) => res.json())
Copy link

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.

Comment on lines +8 to +14
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 }),
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

imageUrl 유효성 검증 누락
message.payload?.urlundefined 인 경우 그대로 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.

Comment on lines +3 to +7
export const handleImageAnalysisFetch = async (
message: any,
sender: any,
sendResponse: any,
) => {
Copy link

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.

Suggested change
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.

Comment on lines +1 to +6
export const handleCosmeticDataFetch = async (
message: any,
sender: any,
sendResponse: any,
) => {
const { productId, html } = message.payload;
Copy link

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.

Comment on lines +27 to +33
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) => {
Copy link

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;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

message.payload 존재 여부 확인 필요
message.payloadundefined 이면 구조 분해 단계에서 런타임 오류가 발생합니다.
호출부 신뢰가 불확실하므로 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.

Comment on lines 70 to 107
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;
}
Copy link

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.

Suggested change
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.

Comment on lines +30 to +54
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 파싱 실패",
});
}
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

네트워크 오류 미처리로 응답 누락 위험
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.

Suggested change
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.

Comment on lines +45 to +65
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(() => {
// 메시지 전송 실패 시 무시
});
}
});
});
};
Copy link

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.

Comment on lines +6 to +20
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);
},
);
}
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants