diff --git a/src/background/handlers/api/cosmeticHandler.ts b/src/background/handlers/api/cosmeticHandler.ts new file mode 100644 index 0000000..0e4bcc1 --- /dev/null +++ b/src/background/handlers/api/cosmeticHandler.ts @@ -0,0 +1,55 @@ +export const handleCosmeticDataFetch = async ( + message: any, + sender: any, + sendResponse: any, +) => { + 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, + }); + } + }); +}; diff --git a/src/background/handlers/api/foodHandler.ts b/src/background/handlers/api/foodHandler.ts new file mode 100644 index 0000000..b975292 --- /dev/null +++ b/src/background/handlers/api/foodHandler.ts @@ -0,0 +1,56 @@ +export const handleFoodDataFetch = async ( + message: any, + sender: any, + sendResponse: any, +) => { + 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 파싱 실패", + }); + } + }); + }); +}; diff --git a/src/background/handlers/api/healthDataHandler.ts b/src/background/handlers/api/healthDataHandler.ts new file mode 100644 index 0000000..136c851 --- /dev/null +++ b/src/background/handlers/api/healthDataHandler.ts @@ -0,0 +1,41 @@ +export const handleHealthDataFetch = async ( + message: any, + sender: any, + sendResponse: any, +) => { + 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 }); + }); +}; diff --git a/src/background/handlers/api/imageAnalysisHandler.ts b/src/background/handlers/api/imageAnalysisHandler.ts new file mode 100644 index 0000000..d4efd36 --- /dev/null +++ b/src/background/handlers/api/imageAnalysisHandler.ts @@ -0,0 +1,42 @@ +import { logger } from "@src/utils/logger"; + +export const handleImageAnalysisFetch = async ( + message: any, + sender: any, + sendResponse: any, +) => { + 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, + }); + }); +}; diff --git a/src/background/handlers/api/index.ts b/src/background/handlers/api/index.ts new file mode 100644 index 0000000..03da307 --- /dev/null +++ b/src/background/handlers/api/index.ts @@ -0,0 +1,6 @@ +export { handleFoodDataFetch } from "./foodHandler"; +export { handleImageAnalysisFetch } from "./imageAnalysisHandler"; +export { handleOutlineInfoFetch } from "./outlineInfoHandler"; +export { handleCosmeticDataFetch } from "./cosmeticHandler"; +export { handleReviewSummaryFetch } from "./reviewSummaryHandler"; +export { handleHealthDataFetch } from "./healthDataHandler"; diff --git a/src/background/handlers/api/outlineInfoHandler.ts b/src/background/handlers/api/outlineInfoHandler.ts new file mode 100644 index 0000000..48cc20e --- /dev/null +++ b/src/background/handlers/api/outlineInfoHandler.ts @@ -0,0 +1,59 @@ +export const handleOutlineInfoFetch = async ( + message: any, + sender: any, + sendResponse: any, +) => { + const { outline, html } = message.payload; + + 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, + }); + }); + }); +}; diff --git a/src/background/handlers/api/reviewSummaryHandler.ts b/src/background/handlers/api/reviewSummaryHandler.ts new file mode 100644 index 0000000..1c63c4a --- /dev/null +++ b/src/background/handlers/api/reviewSummaryHandler.ts @@ -0,0 +1,54 @@ +export const handleReviewSummaryFetch = async ( + message: any, + sender: any, + sendResponse: any, +) => { + 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, + }); + }); +}; diff --git a/src/background/handlers/pageHandlers.ts b/src/background/handlers/pageHandlers.ts new file mode 100644 index 0000000..70a966e --- /dev/null +++ b/src/background/handlers/pageHandlers.ts @@ -0,0 +1,111 @@ +export const handlePageTypeMessage = async ( + message: any, + sender: any, + sendResponse: any, +) => { + 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); + }, + ); + } + }); +}; + +export const handleCartPageMessage = async ( + message: any, + sender: any, + sendResponse: any, +) => { + 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); + }, + ); + } + }); +}; + +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(() => { + // 메시지 전송 실패 시 무시 + }); + } + }); + }); +}; + +export const handleVendorHtmlFetch = async ( + message: any, + sender: any, + sendResponse: any, +) => { + if (sender.tab?.id) { + chrome.tabs.sendMessage( + sender.tab.id, + { type: "GET_VENDOR_HTML" }, + (response) => { + sendResponse(response); + }, + ); + } +}; + +export const handleProductTitleFetch = async ( + message: any, + sender: any, + sendResponse: any, +) => { + chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { + const tabId = tabs[0]?.id; + if (!tabId) { + sendResponse({ title: "" }); + return; + } + + chrome.tabs.sendMessage( + tabId, + { type: "GET_PRODUCT_TITLE" }, + (response) => { + if (chrome.runtime.lastError) { + console.error( + "[voim][background] title 요청 실패:", + chrome.runtime.lastError.message, + ); + sendResponse({ title: "" }); + } else { + sendResponse(response); + } + }, + ); + }); +}; diff --git a/src/background/index.ts b/src/background/index.ts index 72e79f4..38c81cf 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -8,8 +8,22 @@ import { DEFAULT_FONT_WEIGHT, } from "./constants"; import { initCommandListeners } from "./listeners/commandListeners"; -import { handleModalToggle } from "./listeners/modalCommandHandler"; import { handleIconToggle } from "./listeners/iconHandler"; +import { + handleFoodDataFetch, + handleImageAnalysisFetch, + handleOutlineInfoFetch, + handleCosmeticDataFetch, + handleReviewSummaryFetch, + handleHealthDataFetch, +} from "./handlers/api"; +import { + handlePageTypeMessage, + handleCartPageMessage, + handleCartItemsUpdated, + handleVendorHtmlFetch, + handleProductTitleFetch, +} from "./handlers/pageHandlers"; /** * 백그라운드 스크립트 초기화 @@ -54,390 +68,42 @@ chrome.runtime.onInstalled.addListener((details) => { }); 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; } }); @@ -449,34 +115,3 @@ chrome.action.onClicked.addListener(async (tab) => { logger.error("툴바 아이콘 클릭 처리 중 오류 발생:", error); } }); -chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { - if (message.type === "GET_PRODUCT_TITLE") { - chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { - const tabId = tabs[0]?.id; - if (!tabId) { - sendResponse({ title: "" }); - return; - } - - chrome.tabs.sendMessage( - tabId, - { type: "GET_PRODUCT_TITLE" }, - (response) => { - if (chrome.runtime.lastError) { - console.error( - "[voim][background] title 요청 실패:", - chrome.runtime.lastError.message, - ); - sendResponse({ title: "" }); - } else { - sendResponse(response); - } - }, - ); - }); - - return true; - } - - return false; -});