Manifest V3에서 URL 추적 매개변수 제거하기: 흔한 실수 피하는 법
브라우저 확장 프로그램 개발자들은 종종 URL에서 UTM 태그, fbclid, gclid 같은 추적 매개변수를 제거해야 할 때가 있습니다. 정규 표현식을 사용한 간단한 구현은 기본적인 경우에는 작동하지만, Manifest V3에서는 검색 엔진을 망가뜨리거나, 무한 리디렉션을 일으키고, XHR 요청을 방해하는 등 실패로 이어집니다. 실제 아키텍처 예시와 함께 주요 문제점과 해결책을 살펴보겠습니다.
검색 엔진을 위한 허용 목록은 필요한 매개변수가 제거되는 것을 방지합니다. Google, Yandex 등은 소스와 미디어를 필터(예: 이미지, 동영상)에 사용합니다. 예외 없이 제거하면 검색 기능이 작동하지 않습니다.
const WHITELIST = [
'google.com', 'bing.com', 'yandex.ru', 'duckduckgo.com'
];
async function cleanUrl(url) {
const urlObj = new URL(url);
if (WHITELIST.some(site => urlObj.hostname.includes(site))) {
return url; // 검색 엔진은 그대로 둡니다
}
// ... 제거 로직
}
Manifest V3에서의 네비게이션: 전환 필터링
Manifest V3는 요청 가로채기를 제한합니다. declarativeNetRequest는 매개변수를 동적으로 파싱하지 않으므로 webNavigation.onBeforeNavigate를 사용합니다. chrome.tabs.update를 통한 간단한 리디렉션은 문제를 일으킵니다:
- 폼의 POST 요청이 깨집니다.
- JSON을 위한 XHR/Fetch 요청이 실패합니다.
- 리디렉션이 이벤트를 반복적으로 트리거하여 루프를 만듭니다.
해결책은 엄격한 transitionType 확인입니다. 'link'(링크 클릭)만 처리하고, 타이핑, auto_subframe, form_submit은 무시합니다.
chrome.webNavigation.onBeforeNavigate.addListener(async (details) => {
const result = await chrome.storage.sync.get(['autoClean']);
if (!result.autoClean) return;
if (details.transitionType !== 'link') return;
const cleanedUrl = await cleanUrl(details.url);
if (details.url !== cleanedUrl) {
chrome.tabs.update(details.tabId, { url: cleanedUrl });
}
}, {
url: [
{hostContains: '.'},
{schemes: ['http', 'https']}
]
});
hostContains: '.' 필터는 new URL()이 예측 불가능하게 작동하는 chrome-extension:// 스킴을 제외합니다.
컨텍스트 메뉴와 클립보드 API
수동 링크 제거를 위해 컨텍스트 메뉴를 추가하세요. Manifest V3 서비스 워커에서는 클립보드 접근이 차단됩니다—콘텐츠 스크립트에 위임하세요.
background.js에서:
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === "cleanCopy" && info.linkUrl) {
chrome.tabs.sendMessage(tab.id, {
action: "cleanAndCopy",
url: info.linkUrl
});
}
});
콘텐츠 스크립트는 토스트 알림과 함께 Async Clipboard API를 사용합니다:
function cleanAndCopyLink(url) {
chrome.runtime.sendMessage({ action: "cleanLink", url: url }, (response) => {
if (response && response.cleanedUrl) {
navigator.clipboard.writeText(response.cleanedUrl).then(() => {
showNotification(`링크 제거 후 복사 완료!\n제거된 추적기: ${response.trackerCount}`);
});
}
});
}
function showNotification(message) {
let toast = document.getElementById('zerotail-toast');
if (!toast) {
toast = document.createElement('div');
toast.id = 'zerotail-toast';
toast.style.cssText = `
position: fixed; bottom: 20px; right: 20px;
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
color: white; padding: 12px 20px; border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 999999;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 320px; transition: opacity 0.3s ease;
`;
document.body.appendChild(toast);
}
toast.textContent = message;
toast.style.opacity = '1';
setTimeout(() => { toast.style.opacity = '0'; }, 3000);
}
스타일링은 Material Design을 모방합니다: 그라데이션 배경, 그림자, 부드러운 페이드.
핵심 요점
- 도메인 허용 목록: 매개변수가 기능에 중요한 검색 엔진(google.com, yandex.ru)을 제외합니다.
- transitionType === 'link': 폼, XHR, URL 입력과의 간섭을 방지합니다.
- 스킴 필터: hostContains: '.' + http/https로 extension:// 오류를 피합니다.
- 클립보드 위임: Manifest V3를 위해 서비스 워커 → 콘텐츠 스크립트.
- 토스트 UI: 알림 없이 미니멀한 알림, 제거된 추적기 수 표시.
— Editorial Team
아직 댓글이 없습니다.