{% extends 'base.html.twig' %}
{% block title %}Détection Navigateur & Mode Privé{% endblock %}
{% block body %}
<div class="row">
<div class="col-md-12">
<h1>Détection Navigateur & Mode Privé</h1>
<div>
<p><strong>Moteur</strong>: <span id="engine">?</span></p>
<p><strong>Navigateurs</strong>: <span id="flavor">?</span></p>
<p><strong>Score privé</strong>: <span id="score">?</span></p>
<p><strong>Interprétation</strong>: <span id="label">?</span></p>
</div>
<hr/>
<p>Scores: 2 = privé sûr, 1 = non privé sûr, -2 = plutôt privé (incertain), -1 = plutôt non privé (incertain).</p>
</div>
</div>
<script>
(function () {
function mib(x) { return Math.round(((x || 0) / (1024 * 1024))); }
function detectBrowser() {
var nav = navigator;
var ua = navigator.userAgent || '';
var uaData = nav.userAgentData;
if (uaData && uaData.brands && uaData.brands.length) {
var brands = uaData.brands.map(function (b) { return String(b.brand || '').toLowerCase(); }).join(' ');
if (brands.indexOf('chrom') > -1) {
if (brands.indexOf('edge') > -1) return { engine: 'Chromium', flavor: 'Edge' };
if (brands.indexOf('opera') > -1 || brands.indexOf('opr') > -1) return { engine: 'Chromium', flavor: 'Opera' };
if (typeof (nav).brave !== 'undefined') return { engine: 'Chromium', flavor: 'Brave' };
return { engine: 'Chromium', flavor: 'Chrome' };
}
}
var isApple = (navigator.vendor || '').indexOf('Apple') > -1;
var isSafariLike = isApple && /Safari\//.test(ua) && !/Chrome|Chromium|Edg|OPR/.test(ua);
if (isSafariLike) return { engine: 'WebKit', flavor: 'Safari' };
var isFirefox = /Firefox\//.test(ua) && (typeof CSS !== 'undefined' && CSS.supports && CSS.supports('(-moz-appearance: none)'));
if (isFirefox) return { engine: 'Gecko', flavor: 'Firefox' };
if (/Chrome\/|Chromium\/|Edg\/|OPR\//.test(ua)) {
if (/Edg\//.test(ua)) return { engine: 'Chromium', flavor: 'Edge' };
if (/OPR\//.test(ua)) return { engine: 'Chromium', flavor: 'Opera' };
if (typeof (navigator).brave !== 'undefined') return { engine: 'Chromium', flavor: 'Brave' };
return { engine: 'Chromium', flavor: 'Chromium' };
}
return { engine: 'Unknown', flavor: 'Unknown' };
}
async function isIncognitoChromium(flavor) {
var nav = navigator;
var w = window;
var jsHeapMiB = mib((w && w.performance && w.performance.memory && w.performance.memory.jsHeapSizeLimit) || 0) || 1024;
var boundMiB = jsHeapMiB * 2;
if (nav && nav.webkitTemporaryStorage && nav.webkitTemporaryStorage.queryUsageAndQuota) {
var score = await new Promise(function (resolve) {
try {
nav.webkitTemporaryStorage.queryUsageAndQuota(function (_usage, quota) {
var qMiB = mib(quota);
if (qMiB > 0 && qMiB < boundMiB) resolve(2); else resolve(1);
}, function () { resolve(-1); });
} catch (_) { resolve(-1); }
});
if (score !== -1) return score;
}
try {
var est = nav && nav.storage && nav.storage.estimate ? await nav.storage.estimate() : null;
var qMiB = mib(est && est.quota);
var softLow = flavor === 'Brave' ? 192 : 128;
if (qMiB > 0 && qMiB < softLow) return -2;
} catch (_) {}
try {
var persisted = nav && nav.storage && nav.storage.persisted ? await nav.storage.persisted() : undefined;
if (persisted === false) return -1;
} catch (_) {}
return -1;
}
async function isIncognitoSafari() {
// Test 1: IndexedDB write probe
try {
const name = 'probe-' + Math.random().toString(36).slice(2,9);
const wrote = await new Promise((resolve) => {
let settled = false;
const req = indexedDB.open(name, 1);
req.onupgradeneeded = (ev) => {
try {
ev.target.result.createObjectStore('t', { autoIncrement:true }).put(new Blob(['x']));
} catch {}
};
req.onsuccess = (ev) => {
try { ev.target.result.close(); indexedDB.deleteDatabase(name); } catch {}
if (!settled) { settled = true; resolve(true); }
};
req.onerror = () => { if (!settled) { settled = true; resolve(false); } };
});
if (wrote === false) return 2; // privé détecté par IDB
} catch {
return -2; // toute exception => privé
}
// Test 2: OPFS write probe
try {
if (navigator.storage && navigator.storage.getDirectory) {
const root = await navigator.storage.getDirectory();
const file = await root.getFileHandle('probe.txt', { create:true });
const w = await file.createWritable();
await w.write(new Blob(['x']));
await w.close();
// écriture OK -> ne conclut pas au privé
}
} catch (e) {
const errName = (e && e.name) ? e.name : String(e || '');
if (errName === "UnknownError") return 2; // privé détecté par OPFS
}
return 1; // par défaut: normal
}
async function isIncognitoFirefox() {
try {
const hasOPFS = !!(navigator.storage && navigator.storage.getDirectory);
if (hasOPFS) {
const root = await navigator.storage.getDirectory();
const file = await root.getFileHandle('probe.txt', { create: true });
const w = await file.createWritable();
await w.write(new Blob(['x'])); await w.close();
}
} catch {
return -2;
}
return -1;
}
async function detectIncognitoMode() {
var b = detectBrowser();
if (b.engine === 'Chromium') return { engine: b.engine, flavor: b.flavor, score: await isIncognitoChromium(b.flavor) };
if (b.engine === 'WebKit') return { engine: b.engine, flavor: b.flavor, score: await isIncognitoSafari() };
if (b.engine === 'Gecko') return { engine: b.engine, flavor: b.flavor, score: await isIncognitoFirefox() };
return { engine: b.engine, flavor: b.flavor, score: -1 };
}
function labelForScore(score) {
if (score === 2) return 'Privé (sûr)';
if (score === 1) return 'Non privé (sûr)';
if (score === -2) return 'Plutôt privé (incertain)';
return 'Plutôt non privé (incertain)';
}
document.addEventListener('DOMContentLoaded', function () {
detectIncognitoMode().then(function (res) {
var engineEl = document.getElementById('engine');
var flavorEl = document.getElementById('flavor');
var scoreEl = document.getElementById('score');
var labelEl = document.getElementById('label');
if (engineEl) engineEl.textContent = String(res.engine);
if (flavorEl) flavorEl.textContent = String(res.flavor);
if (scoreEl) scoreEl.textContent = String(res.score);
if (labelEl) labelEl.textContent = labelForScore(res.score);
}).catch(function () {
var engineEl = document.getElementById('engine');
var flavorEl = document.getElementById('flavor');
var scoreEl = document.getElementById('score');
var labelEl = document.getElementById('label');
if (engineEl) engineEl.textContent = 'Unknown';
if (flavorEl) flavorEl.textContent = 'Unknown';
if (scoreEl) scoreEl.textContent = '-1';
if (labelEl) labelEl.textContent = labelForScore(-1);
});
});
})();
/*
# For test
(async () => {
const mib = v => Math.round((v ?? 0) / (1024 * 1024));
const logG = (title) => { console.group(title); console.time(title); return () => { console.timeEnd(title); console.groupEnd(); }; };
// 1) navigator.storage.estimate()
async function testStorageEstimate() {
const end = logG('Test: navigator.storage.estimate()');
try {
if (!navigator.storage?.estimate) {
console.info('API not available');
return { ok:false, note:'no-api' };
}
const { quota, usage } = await navigator.storage.estimate();
const out = { ok:true, quota, usage, quotaMB: mib(quota), usageMB: mib(usage) };
console.log('Result:', out);
return out;
} catch (e) {
console.warn('Error:', e?.name || e);
return { ok:false, note:'error' };
} finally { end(); }
}
// 2) navigator.webkitTemporaryStorage.queryUsageAndQuota (Chromium legacy)
async function testWebkitTemporaryQuota(timeout = 2000) {
const end = logG('Test: navigator.webkitTemporaryStorage.queryUsageAndQuota');
const api = navigator?.webkitTemporaryStorage?.queryUsageAndQuota;
if (!api) {
console.info('API not available');
end(); return { ok:false, note:'no-api' };
}
const res = await new Promise(resolve => {
let settled = false;
try {
navigator.webkitTemporaryStorage.queryUsageAndQuota(
(usage, quota) => { if (settled) return; settled = true; resolve({ ok:true, usage, quota, usageMB: mib(usage), quotaMB: mib(quota) }); },
() => { if (settled) return; settled = true; resolve({ ok:false, note:'rejected' }); }
);
setTimeout(() => { if (!settled) { settled = true; resolve({ ok:false, note:'timeout' }); } }, timeout);
} catch (e) { resolve({ ok:false, note:'throw', err:e?.name || String(e) }); }
});
console.log('Result:', res);
end(); return res;
}
// 3) navigator.storage.persisted()
async function testPersisted() {
const end = logG('Test: navigator.storage.persisted()');
try {
if (!navigator.storage?.persisted) {
console.info('API not available');
return { ok:false, note:'no-api' };
}
const persisted = await navigator.storage.persisted();
const out = { ok:true, persisted: !!persisted };
console.log('Result:', out);
return out;
} catch (e) {
console.warn('Error:', e?.name || e);
return { ok:false, note:'error' };
} finally { end(); }
}
// 4) indexedDB.databases()
async function testIndexedDBDatabases() {
const end = logG('Test: indexedDB.databases()');
try {
if (!indexedDB?.databases) {
console.info('API not available');
return { ok:false, note:'no-api' };
}
const dbs = await indexedDB.databases();
const out = { ok:true, dbsCount: Array.isArray(dbs) ? dbs.length : 0, dbs };
console.log('Result:', out);
return out;
} catch (e) {
console.warn('Error:', e?.name || e);
return { ok:false, note:'error' };
} finally { end(); }
}
// 5) indexedDB.open() + write Blob
async function testIndexedDBWriteProbe(timeout = 2000) {
const end = logG('Test: indexedDB.open() + write Blob');
const name = 'probe-' + Math.random().toString(36).slice(2,9);
const res = await new Promise(resolve => {
let settled=false;
try {
const req = indexedDB.open(name, 1);
req.onupgradeneeded = ev => {
try {
const db = ev.target.result;
db.createObjectStore('t', { autoIncrement:true }).put(new Blob(['x']));
} catch {}
};
req.onsuccess = ev => {
try { ev.target.result.close(); indexedDB.deleteDatabase(name); } catch {}
if (!settled) { settled=true; resolve({ ok:true, wrote:true }); }
};
req.onerror = () => { if (!settled) { settled=true; resolve({ ok:true, wrote:false }); } };
setTimeout(() => { if (!settled) { settled=true; resolve({ ok:false, note:'timeout' }); } }, timeout);
} catch (e) {
resolve({ ok:false, note:'throw', err:e?.name || String(e) });
}
});
console.log('Result:', res);
end(); return res;
}
// 6) navigator.serviceWorker.register()
async function testServiceWorkerRegister(scriptUrl = '/sw-probe.js', timeout = 3000) {
const end = logG('Test: navigator.serviceWorker.register()');
if (!('serviceWorker' in navigator)) {
console.info('API not available');
end(); return { ok:false, note:'no-api' };
}
const isSecure = window.isSecureContext;
const sameOrigin = scriptUrl.startsWith('/') || scriptUrl.startsWith(location.origin);
console.log('Precheck:', { isSecure, sameOrigin });
if (!isSecure || !sameOrigin) {
console.info('Skipping (needs HTTPS + same-origin script)');
end(); return { ok:false, note:'skipped' };
}
try {
const reg = await Promise.race([
navigator.serviceWorker.register(scriptUrl),
new Promise((_, rej)=>setTimeout(()=>rej(new Error('timeout')), timeout))
]);
console.log('Registered:', true);
try { await reg.unregister(); console.log('Unregistered: OK'); } catch {}
end(); return { ok:true, registered:true };
} catch (e) {
console.log('Registered:', false, 'Error:', e?.name || String(e));
end(); return { ok:true, registered:false };
}
}
// 7) CacheStorage (open/put/match)
async function testCacheStorage() {
const end = logG('Test: CacheStorage (open/put/match)');
if (!('caches' in window)) {
console.info('API not available');
end(); return { ok:false, note:'no-api' };
}
try {
const name = 'probe-cache-'+Math.random().toString(36).slice(2,6);
const c = await caches.open(name);
await c.put('/__probe__', new Response('x', { headers: { 'content-type':'text/plain' } }));
const r = await c.match('/__probe__');
const out = { ok:true, cacheWorks: !!r };
console.log('Result:', out);
end(); return out;
} catch (e) {
console.log('Write/match failed');
end(); return { ok:true, cacheWorks:false };
}
}
// 8) Cookies (classic) + cookieEnabled
function testCookies() {
const end = logG('Test: document.cookie / navigator.cookieEnabled');
try {
document.cookie = "__probe="+Date.now()+"; path=/";
const enabled = navigator.cookieEnabled && document.cookie.includes('__probe=');
document.cookie = "__probe=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
const out = { ok:true, cookieEnabled: !!enabled, navigatorCookieEnabled: !!navigator.cookieEnabled };
console.log('Result:', out);
return out;
} catch (e) {
console.warn('Error:', e?.name || e);
return { ok:false, note:'error' };
} finally { end(); }
}
// 9) OPFS: navigator.storage.getDirectory()
async function testOPFS() {
const end = logG('Test: OPFS (navigator.storage.getDirectory)');
const has = !!(navigator.storage && navigator.storage.getDirectory);
console.log('Available:', has);
if (!has) { end(); return { ok:false, note:'no-api' }; }
try {
const root = await navigator.storage.getDirectory();
const file = await root.getFileHandle('probe.txt', { create:true });
const w = await file.createWritable();
await w.write(new Blob(['x'])); await w.close();
const out = { ok:true, opfsWrite:true };
console.log('Result:', out);
end(); return out;
} catch (e) {
const out = { ok:true, opfsWrite:false, err:e?.name || String(e) };
console.log('Result:', out);
end(); return out;
}
}
// 10) Storage Buckets
async function testStorageBuckets() {
const end = logG('Test: navigator.storageBuckets');
const has = !!(navigator.storageBuckets);
console.log('Available:', has);
if (!has) { end(); return { ok:false, note:'no-api' }; }
try {
const b = await navigator.storageBuckets.open('probe-bucket');
const est = await b.estimate?.();
const out = { ok:true, estimate: est ?? null };
console.log('Result:', out);
end(); return out;
} catch (e) {
const out = { ok:false, note:'error', err:e?.name || String(e) };
console.log('Result:', out);
end(); return out;
}
}
// 11) File System Access pickers (presence only, no UI)
function testFileSystemAccessPickers() {
const end = logG('Test: File System Access pickers presence');
const has = ('showOpenFilePicker' in window) || ('showSaveFilePicker' in window);
const out = { ok:true, present: has };
console.log('Result:', out);
end(); return out;
}
// 12) PushManager presence
function testPushManager() {
const end = logG('Test: PushManager presence');
const has = 'PushManager' in window;
const out = { ok:true, present: has };
console.log('Result:', out);
end(); return out;
}
// 13) Permissions API (notifications state)
async function testPermissionsNotifications() {
const end = logG('Test: Permissions API (notifications)');
if (!(navigator.permissions?.query)) {
console.info('API not available');
end(); return { ok:false, note:'no-api' };
}
try {
const st = await navigator.permissions.query({ name: 'notifications' });
const out = { ok:true, state: st.state }; // 'granted'|'denied'|'prompt'
console.log('Result:', out);
end(); return out;
} catch (e) {
console.warn('Error:', e?.name || e);
end(); return { ok:false, note:'error' };
}
}
// 14) cookieStore presence
function testCookieStore() {
const end = logG('Test: cookieStore presence');
const has = 'cookieStore' in window;
const out = { ok:true, present: has };
console.log('Result:', out);
end(); return out;
}
// 15) navigator.userAgentData presence (Chromium)
function testUserAgentData() {
const end = logG('Test: navigator.userAgentData presence');
const d = (navigator).userAgentData;
const out = { ok:true, present: !!d, brands: d?.brands || null, mobile: d?.mobile ?? null };
console.log('Result:', out);
end(); return out;
}
// =====================
// Run sequentially
// =====================
const results = {};
results.storageEstimate = await testStorageEstimate();
results.webkitTemporaryQuota = await testWebkitTemporaryQuota();
results.persisted = await testPersisted();
results.idbDatabases = await testIndexedDBDatabases();
results.idbWriteProbe = await testIndexedDBWriteProbe();
results.serviceWorkerRegister = await testServiceWorkerRegister('/sw-probe.js');
results.cacheStorage = await testCacheStorage();
results.cookies = testCookies();
results.opfs = await testOPFS();
results.storageBuckets = await testStorageBuckets();
results.fsAccessPickers = testFileSystemAccessPickers();
results.pushManager = testPushManager();
results.permissionsNotifications= await testPermissionsNotifications();
results.cookieStore = testCookieStore();
results.userAgentData = testUserAgentData();
console.group('All test results');
console.dir(results);
console.groupEnd();
// Return the object for convenience
results;
})();
*/
</script>
{% endblock %}