// background/index.ts
// Le service worker Manifest V3 peut etre tue a tout moment.
// Persister l'etat critique dans chrome.storage.local.
chrome.runtime.onInstalled.addListener(async (details) => {
if (details.reason === 'install') {
// Premier install : initialiser le storage
await chrome.storage.local.set({ sessionActive: false, rules: [] });
}
});
// Au reveil du service worker : restaurer l'etat
async function restoreState(): Promise<void> {
const data = await chrome.storage.local.get(['sessionActive', 'currentSession']);
if (data.sessionActive && data.currentSession) {
await applyBlockingRules(data.currentSession);
}
}
restoreState();
// Utiliser les alarms pour les timers (les setTimeout ne survivent pas au kill)
chrome.alarms.onAlarm.addListener(async (alarm) => {
if (alarm.name === 'session-tick') {
await handleSessionTick();
}
if (alarm.name === 'session-end') {
await handleSessionEnd();
}
});
5. Native Messaging avec l'app desktop
// background/native-messaging.ts
const NATIVE_HOST = 'com.focusshield.connector';
let port: chrome.runtime.Port | null = null;
function connectToDesktop(): chrome.runtime.Port {
port = chrome.runtime.connectNative(NATIVE_HOST);
port.onMessage.addListener((message: unknown) => {
// Messages venant du desktop
handleDesktopMessage(message);
});
port.onDisconnect.addListener(() => {
port = null;
// Fallback vers WebSocket si Native Messaging echoue
connectViaWebSocket();
});
return port;
}
function sendToDesktop(message: object): void {
if (port) {
port.postMessage(message);
} else {
sendViaWebSocket(message);
}
}
6. Page de blocage
// blocked/App.tsx
function BlockedPage() {
const [session, setSession] = useState<SessionInfo | null>(null);
const [quote, setQuote] = useState<Quote | null>(null);
const [attemptCount, setAttemptCount] = useState(0);
useEffect(() => {
// Recuperer les infos de session depuis le background
sendMessage('session:status', {}).then(setSession);
// Charger une citation random
loadRandomQuote().then(setQuote);
// Logger la tentative de distraction
const url = new URL(window.location.href);
const blockedDomain = url.searchParams.get('domain') || 'unknown';
sendMessage('stats:attempt', {
domain: blockedDomain,
timestamp: Date.now()
});
}, []);
return (
<div className="min-h-screen flex flex-col items-center justify-center bg-gray-50 dark:bg-gray-900 p-8">
<div className="max-w-md text-center space-y-6">
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">
Site bloque
</h1>
{session && <SessionTimer endTime={session.endTime} />}
{quote && (
<blockquote className="text-gray-600 dark:text-gray-400 italic">
"{quote.text}" — {quote.author}
</blockquote>
)}
<p className="text-sm text-gray-500">
{attemptCount} tentative(s) bloquee(s) cette session
</p>
<button
onClick={() => window.history.back()}
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
Retour au travail
</button>
</div>
</div>
);
}
Anti-patterns a eviter
Interdit
Pourquoi
Alternative
any
Pas de type safety
@types/chrome, types custom
setTimeout/setInterval dans le service worker
Ne survit pas au kill du SW
chrome.alarms API
Stocker l'etat en memoire du service worker
Perdu au redemarrage
chrome.storage.local
webRequest API (Manifest V2)
Deprecie dans MV3
declarativeNetRequest
eval() ou new Function()
CSP de MV3 l'interdit
Logique statique
document dans le service worker
Pas de DOM dans le SW
Message passing vers content/popup
window.fetch pour la communication desktop
Pas fiable, CORS
Native Messaging ou WebSocket
Permissions excessives dans le manifest
Rejet par les stores
Permissions minimales, optional_permissions
Static rules > 300k
Limite Chrome
Dynamic rules pour le runtime
console.log en production
Bruit, perf
Logger conditionnel ou suppression
Ignorer Firefox
15% du marche
browser polyfill, tester sur les deux
Contraintes Manifest V3
Service worker : pas de DOM, pas de window, pas d'etat persistant en memoire
CSP : pas de eval, pas de unsafe-inline, pas de remote code
declarativeNetRequest : max 5000 dynamic rules, max 300000 static rules
Alarms : minimum 1 minute d'intervalle (en release, 30s en dev)
Storage : chrome.storage.local = 10MB max, chrome.storage.sync = 100KB max
Native Messaging : messages max 1MB, necessite un host manifest natif installe
Incognito : opt-in par l'utilisateur, pas force par l'extension
Cross-browser compatibility
API
Chrome
Firefox
Strategie
declarativeNetRequest
Oui
Oui (MV3)
Standard
chrome.runtime
chrome.*
browser.*
Polyfill webextension-polyfill
Native Messaging
Oui
Oui
Meme API, manifest host different
Incognito
spanning mode
spanning mode
Standard
chrome.alarms
Oui
Oui
Standard
Service worker
Oui
Oui (FF 109+)
Minimum FF 109
Utiliser webextension-polyfill pour normaliser les APIs :
import browser from 'webextension-polyfill';
// Utiliser browser.* au lieu de chrome.* pour la compatibilite
Mission
Implemente dans l'extension navigateur : {{input}}
Methodologie
Analyse — Lis le manifest.json et les fichiers existants dans apps/browser-extension/src/
Manifest — Verifie que les permissions necessaires sont declarees
Implementation — Code avec les patterns ci-dessus, respecte les contraintes MV3
Message passing — Si communication inter-contextes necessaire, utilise le pattern type-safe
Compatibilite — Verifie que le code fonctionne sur Chrome ET Firefox
Build — Verifie le build multi-entry :
# Build de l'extension
npm run build -- --filter browser-extension
# Type check
npx tsc --noEmit -p apps/browser-extension/tsconfig.json
# Tests
npx vitest run apps/browser-extension