templates/browser-detect/browser-detect.html.twig line 1

Open in your IDE?
  1. {% extends 'base.html.twig' %}
  2. {% block title %}Détection Navigateur & Mode Privé{% endblock %}
  3. {% block body %}
  4. <div class="row">
  5.     <div class="col-md-12">
  6.         <h1>Détection Navigateur & Mode Privé</h1>
  7.         <div>
  8.             <p><strong>Moteur</strong>: <span id="engine">?</span></p>
  9.             <p><strong>Navigateurs</strong>: <span id="flavor">?</span></p>
  10.             <p><strong>Score privé</strong>: <span id="score">?</span></p>
  11.             <p><strong>Interprétation</strong>: <span id="label">?</span></p>
  12.         </div>
  13.         <hr/>
  14.         <p>Scores: 2 = privé sûr, 1 = non privé sûr, -2 = plutôt privé (incertain), -1 = plutôt non privé (incertain).</p>
  15.     </div>
  16. </div>
  17. <script>
  18. (function () {
  19.     function mib(x) { return Math.round(((x || 0) / (1024 * 1024))); }
  20.     function detectBrowser() {
  21.         var nav = navigator;
  22.         var ua = navigator.userAgent || '';
  23.         var uaData = nav.userAgentData;
  24.         if (uaData && uaData.brands && uaData.brands.length) {
  25.             var brands = uaData.brands.map(function (b) { return String(b.brand || '').toLowerCase(); }).join(' ');
  26.             if (brands.indexOf('chrom') > -1) {
  27.                 if (brands.indexOf('edge') > -1) return { engine: 'Chromium', flavor: 'Edge' };
  28.                 if (brands.indexOf('opera') > -1 || brands.indexOf('opr') > -1) return { engine: 'Chromium', flavor: 'Opera' };
  29.                 if (typeof (nav).brave !== 'undefined') return { engine: 'Chromium', flavor: 'Brave' };
  30.                 return { engine: 'Chromium', flavor: 'Chrome' };
  31.             }
  32.         }
  33.         var isApple = (navigator.vendor || '').indexOf('Apple') > -1;
  34.         var isSafariLike = isApple && /Safari\//.test(ua) && !/Chrome|Chromium|Edg|OPR/.test(ua);
  35.         if (isSafariLike) return { engine: 'WebKit', flavor: 'Safari' };
  36.         var isFirefox = /Firefox\//.test(ua) && (typeof CSS !== 'undefined' && CSS.supports && CSS.supports('(-moz-appearance: none)'));
  37.         if (isFirefox) return { engine: 'Gecko', flavor: 'Firefox' };
  38.         if (/Chrome\/|Chromium\/|Edg\/|OPR\//.test(ua)) {
  39.             if (/Edg\//.test(ua)) return { engine: 'Chromium', flavor: 'Edge' };
  40.             if (/OPR\//.test(ua)) return { engine: 'Chromium', flavor: 'Opera' };
  41.             if (typeof (navigator).brave !== 'undefined') return { engine: 'Chromium', flavor: 'Brave' };
  42.             return { engine: 'Chromium', flavor: 'Chromium' };
  43.         }
  44.         return { engine: 'Unknown', flavor: 'Unknown' };
  45.     }
  46.     async function isIncognitoChromium(flavor) {
  47.         var nav = navigator;
  48.         var w = window;
  49.         var jsHeapMiB = mib((w && w.performance && w.performance.memory && w.performance.memory.jsHeapSizeLimit) || 0) || 1024;
  50.         var boundMiB = jsHeapMiB * 2;
  51.         if (nav && nav.webkitTemporaryStorage && nav.webkitTemporaryStorage.queryUsageAndQuota) {
  52.             var score = await new Promise(function (resolve) {
  53.                 try {
  54.                     nav.webkitTemporaryStorage.queryUsageAndQuota(function (_usage, quota) {
  55.                         var qMiB = mib(quota);
  56.                         if (qMiB > 0 && qMiB < boundMiB) resolve(2); else resolve(1);
  57.                     }, function () { resolve(-1); });
  58.                 } catch (_) { resolve(-1); }
  59.             });
  60.             if (score !== -1) return score;
  61.         }
  62.         try {
  63.             var est = nav && nav.storage && nav.storage.estimate ? await nav.storage.estimate() : null;
  64.             var qMiB = mib(est && est.quota);
  65.             var softLow = flavor === 'Brave' ? 192 : 128;
  66.             if (qMiB > 0 && qMiB < softLow) return -2;
  67.         } catch (_) {}
  68.         try {
  69.             var persisted = nav && nav.storage && nav.storage.persisted ? await nav.storage.persisted() : undefined;
  70.             if (persisted === false) return -1;
  71.         } catch (_) {}
  72.         return -1;
  73.     }
  74.     async function isIncognitoSafari() {
  75.       // Test 1: IndexedDB write probe
  76.       try {
  77.         const name = 'probe-' + Math.random().toString(36).slice(2,9);
  78.         const wrote = await new Promise((resolve) => {
  79.           let settled = false;
  80.           const req = indexedDB.open(name, 1);
  81.           req.onupgradeneeded = (ev) => {
  82.             try {
  83.               ev.target.result.createObjectStore('t', { autoIncrement:true }).put(new Blob(['x']));
  84.             } catch {}
  85.           };
  86.           req.onsuccess = (ev) => {
  87.             try { ev.target.result.close(); indexedDB.deleteDatabase(name); } catch {}
  88.             if (!settled) { settled = true; resolve(true); }
  89.           };
  90.           req.onerror = () => { if (!settled) { settled = true; resolve(false); } };
  91.         });
  92.         if (wrote === false) return 2; // privé détecté par IDB
  93.       } catch {
  94.         return -2; // toute exception => privé
  95.       }
  96.       // Test 2: OPFS write probe
  97.       try {
  98.         if (navigator.storage && navigator.storage.getDirectory) {
  99.           const root = await navigator.storage.getDirectory();
  100.           const file = await root.getFileHandle('probe.txt', { create:true });
  101.           const w = await file.createWritable();
  102.           await w.write(new Blob(['x']));
  103.           await w.close();
  104.           // écriture OK -> ne conclut pas au privé
  105.         }
  106.       } catch (e) {
  107.         const errName = (e && e.name) ? e.name : String(e || '');
  108.         if (errName === "UnknownError") return 2; // privé détecté par OPFS
  109.       }
  110.       return 1; // par défaut: normal
  111.     }
  112.     async function isIncognitoFirefox() {
  113.         try {
  114.             const hasOPFS = !!(navigator.storage && navigator.storage.getDirectory);
  115.             if (hasOPFS) {
  116.                 const root = await navigator.storage.getDirectory();
  117.                 const file = await root.getFileHandle('probe.txt', { create: true });
  118.                 const w = await file.createWritable();
  119.                 await w.write(new Blob(['x'])); await w.close();
  120.             }
  121.         } catch {
  122.             return -2;
  123.         }
  124.         return -1;
  125.     }
  126.     async function detectIncognitoMode() {
  127.         var b = detectBrowser();
  128.         if (b.engine === 'Chromium') return { engine: b.engine, flavor: b.flavor, score: await isIncognitoChromium(b.flavor) };
  129.         if (b.engine === 'WebKit') return { engine: b.engine, flavor: b.flavor, score: await isIncognitoSafari() };
  130.         if (b.engine === 'Gecko') return { engine: b.engine, flavor: b.flavor, score: await isIncognitoFirefox() };
  131.         return { engine: b.engine, flavor: b.flavor, score: -1 };
  132.     }
  133.     function labelForScore(score) {
  134.         if (score === 2) return 'Privé (sûr)';
  135.         if (score === 1) return 'Non privé (sûr)';
  136.         if (score === -2) return 'Plutôt privé (incertain)';
  137.         return 'Plutôt non privé (incertain)';
  138.     }
  139.     document.addEventListener('DOMContentLoaded', function () {
  140.         detectIncognitoMode().then(function (res) {
  141.             var engineEl = document.getElementById('engine');
  142.             var flavorEl = document.getElementById('flavor');
  143.             var scoreEl = document.getElementById('score');
  144.             var labelEl = document.getElementById('label');
  145.             if (engineEl) engineEl.textContent = String(res.engine);
  146.             if (flavorEl) flavorEl.textContent = String(res.flavor);
  147.             if (scoreEl) scoreEl.textContent = String(res.score);
  148.             if (labelEl) labelEl.textContent = labelForScore(res.score);
  149.         }).catch(function () {
  150.             var engineEl = document.getElementById('engine');
  151.             var flavorEl = document.getElementById('flavor');
  152.             var scoreEl = document.getElementById('score');
  153.             var labelEl = document.getElementById('label');
  154.             if (engineEl) engineEl.textContent = 'Unknown';
  155.             if (flavorEl) flavorEl.textContent = 'Unknown';
  156.             if (scoreEl) scoreEl.textContent = '-1';
  157.             if (labelEl) labelEl.textContent = labelForScore(-1);
  158.         });
  159.     });
  160. })();
  161. /*
  162. # For test
  163. (async () => {
  164.   const mib = v => Math.round((v ?? 0) / (1024 * 1024));
  165.   const logG = (title) => { console.group(title); console.time(title); return () => { console.timeEnd(title); console.groupEnd(); }; };
  166.   // 1) navigator.storage.estimate()
  167.   async function testStorageEstimate() {
  168.     const end = logG('Test: navigator.storage.estimate()');
  169.     try {
  170.       if (!navigator.storage?.estimate) {
  171.         console.info('API not available');
  172.         return { ok:false, note:'no-api' };
  173.       }
  174.       const { quota, usage } = await navigator.storage.estimate();
  175.       const out = { ok:true, quota, usage, quotaMB: mib(quota), usageMB: mib(usage) };
  176.       console.log('Result:', out);
  177.       return out;
  178.     } catch (e) {
  179.       console.warn('Error:', e?.name || e);
  180.       return { ok:false, note:'error' };
  181.     } finally { end(); }
  182.   }
  183.   // 2) navigator.webkitTemporaryStorage.queryUsageAndQuota (Chromium legacy)
  184.   async function testWebkitTemporaryQuota(timeout = 2000) {
  185.     const end = logG('Test: navigator.webkitTemporaryStorage.queryUsageAndQuota');
  186.     const api = navigator?.webkitTemporaryStorage?.queryUsageAndQuota;
  187.     if (!api) {
  188.       console.info('API not available');
  189.       end(); return { ok:false, note:'no-api' };
  190.     }
  191.     const res = await new Promise(resolve => {
  192.       let settled = false;
  193.       try {
  194.         navigator.webkitTemporaryStorage.queryUsageAndQuota(
  195.           (usage, quota) => { if (settled) return; settled = true; resolve({ ok:true, usage, quota, usageMB: mib(usage), quotaMB: mib(quota) }); },
  196.           () => { if (settled) return; settled = true; resolve({ ok:false, note:'rejected' }); }
  197.         );
  198.         setTimeout(() => { if (!settled) { settled = true; resolve({ ok:false, note:'timeout' }); } }, timeout);
  199.       } catch (e) { resolve({ ok:false, note:'throw', err:e?.name || String(e) }); }
  200.     });
  201.     console.log('Result:', res);
  202.     end(); return res;
  203.   }
  204.   // 3) navigator.storage.persisted()
  205.   async function testPersisted() {
  206.     const end = logG('Test: navigator.storage.persisted()');
  207.     try {
  208.       if (!navigator.storage?.persisted) {
  209.         console.info('API not available');
  210.         return { ok:false, note:'no-api' };
  211.       }
  212.       const persisted = await navigator.storage.persisted();
  213.       const out = { ok:true, persisted: !!persisted };
  214.       console.log('Result:', out);
  215.       return out;
  216.     } catch (e) {
  217.       console.warn('Error:', e?.name || e);
  218.       return { ok:false, note:'error' };
  219.     } finally { end(); }
  220.   }
  221.   // 4) indexedDB.databases()
  222.   async function testIndexedDBDatabases() {
  223.     const end = logG('Test: indexedDB.databases()');
  224.     try {
  225.       if (!indexedDB?.databases) {
  226.         console.info('API not available');
  227.         return { ok:false, note:'no-api' };
  228.       }
  229.       const dbs = await indexedDB.databases();
  230.       const out = { ok:true, dbsCount: Array.isArray(dbs) ? dbs.length : 0, dbs };
  231.       console.log('Result:', out);
  232.       return out;
  233.     } catch (e) {
  234.       console.warn('Error:', e?.name || e);
  235.       return { ok:false, note:'error' };
  236.     } finally { end(); }
  237.   }
  238.   // 5) indexedDB.open() + write Blob
  239.   async function testIndexedDBWriteProbe(timeout = 2000) {
  240.     const end = logG('Test: indexedDB.open() + write Blob');
  241.     const name = 'probe-' + Math.random().toString(36).slice(2,9);
  242.     const res = await new Promise(resolve => {
  243.       let settled=false;
  244.       try {
  245.         const req = indexedDB.open(name, 1);
  246.         req.onupgradeneeded = ev => {
  247.           try {
  248.             const db = ev.target.result;
  249.             db.createObjectStore('t', { autoIncrement:true }).put(new Blob(['x']));
  250.           } catch {}
  251.         };
  252.         req.onsuccess = ev => {
  253.           try { ev.target.result.close(); indexedDB.deleteDatabase(name); } catch {}
  254.           if (!settled) { settled=true; resolve({ ok:true, wrote:true }); }
  255.         };
  256.         req.onerror = () => { if (!settled) { settled=true; resolve({ ok:true, wrote:false }); } };
  257.         setTimeout(() => { if (!settled) { settled=true; resolve({ ok:false, note:'timeout' }); } }, timeout);
  258.       } catch (e) {
  259.         resolve({ ok:false, note:'throw', err:e?.name || String(e) });
  260.       }
  261.     });
  262.     console.log('Result:', res);
  263.     end(); return res;
  264.   }
  265.   // 6) navigator.serviceWorker.register()
  266.   async function testServiceWorkerRegister(scriptUrl = '/sw-probe.js', timeout = 3000) {
  267.     const end = logG('Test: navigator.serviceWorker.register()');
  268.     if (!('serviceWorker' in navigator)) {
  269.       console.info('API not available');
  270.       end(); return { ok:false, note:'no-api' };
  271.     }
  272.     const isSecure = window.isSecureContext;
  273.     const sameOrigin = scriptUrl.startsWith('/') || scriptUrl.startsWith(location.origin);
  274.     console.log('Precheck:', { isSecure, sameOrigin });
  275.     if (!isSecure || !sameOrigin) {
  276.       console.info('Skipping (needs HTTPS + same-origin script)');
  277.       end(); return { ok:false, note:'skipped' };
  278.     }
  279.     try {
  280.       const reg = await Promise.race([
  281.         navigator.serviceWorker.register(scriptUrl),
  282.         new Promise((_, rej)=>setTimeout(()=>rej(new Error('timeout')), timeout))
  283.       ]);
  284.       console.log('Registered:', true);
  285.       try { await reg.unregister(); console.log('Unregistered: OK'); } catch {}
  286.       end(); return { ok:true, registered:true };
  287.     } catch (e) {
  288.       console.log('Registered:', false, 'Error:', e?.name || String(e));
  289.       end(); return { ok:true, registered:false };
  290.     }
  291.   }
  292.   // 7) CacheStorage (open/put/match)
  293.   async function testCacheStorage() {
  294.     const end = logG('Test: CacheStorage (open/put/match)');
  295.     if (!('caches' in window)) {
  296.       console.info('API not available');
  297.       end(); return { ok:false, note:'no-api' };
  298.     }
  299.     try {
  300.       const name = 'probe-cache-'+Math.random().toString(36).slice(2,6);
  301.       const c = await caches.open(name);
  302.       await c.put('/__probe__', new Response('x', { headers: { 'content-type':'text/plain' } }));
  303.       const r = await c.match('/__probe__');
  304.       const out = { ok:true, cacheWorks: !!r };
  305.       console.log('Result:', out);
  306.       end(); return out;
  307.     } catch (e) {
  308.       console.log('Write/match failed');
  309.       end(); return { ok:true, cacheWorks:false };
  310.     }
  311.   }
  312.   // 8) Cookies (classic) + cookieEnabled
  313.   function testCookies() {
  314.     const end = logG('Test: document.cookie / navigator.cookieEnabled');
  315.     try {
  316.       document.cookie = "__probe="+Date.now()+"; path=/";
  317.       const enabled = navigator.cookieEnabled && document.cookie.includes('__probe=');
  318.       document.cookie = "__probe=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
  319.       const out = { ok:true, cookieEnabled: !!enabled, navigatorCookieEnabled: !!navigator.cookieEnabled };
  320.       console.log('Result:', out);
  321.       return out;
  322.     } catch (e) {
  323.       console.warn('Error:', e?.name || e);
  324.       return { ok:false, note:'error' };
  325.     } finally { end(); }
  326.   }
  327.   // 9) OPFS: navigator.storage.getDirectory()
  328.   async function testOPFS() {
  329.     const end = logG('Test: OPFS (navigator.storage.getDirectory)');
  330.     const has = !!(navigator.storage && navigator.storage.getDirectory);
  331.     console.log('Available:', has);
  332.     if (!has) { end(); return { ok:false, note:'no-api' }; }
  333.     try {
  334.       const root = await navigator.storage.getDirectory();
  335.       const file = await root.getFileHandle('probe.txt', { create:true });
  336.       const w = await file.createWritable();
  337.       await w.write(new Blob(['x'])); await w.close();
  338.       const out = { ok:true, opfsWrite:true };
  339.       console.log('Result:', out);
  340.       end(); return out;
  341.     } catch (e) {
  342.       const out = { ok:true, opfsWrite:false, err:e?.name || String(e) };
  343.       console.log('Result:', out);
  344.       end(); return out;
  345.     }
  346.   }
  347.   // 10) Storage Buckets
  348.   async function testStorageBuckets() {
  349.     const end = logG('Test: navigator.storageBuckets');
  350.     const has = !!(navigator.storageBuckets);
  351.     console.log('Available:', has);
  352.     if (!has) { end(); return { ok:false, note:'no-api' }; }
  353.     try {
  354.       const b = await navigator.storageBuckets.open('probe-bucket');
  355.       const est = await b.estimate?.();
  356.       const out = { ok:true, estimate: est ?? null };
  357.       console.log('Result:', out);
  358.       end(); return out;
  359.     } catch (e) {
  360.       const out = { ok:false, note:'error', err:e?.name || String(e) };
  361.       console.log('Result:', out);
  362.       end(); return out;
  363.     }
  364.   }
  365.   // 11) File System Access pickers (presence only, no UI)
  366.   function testFileSystemAccessPickers() {
  367.     const end = logG('Test: File System Access pickers presence');
  368.     const has = ('showOpenFilePicker' in window) || ('showSaveFilePicker' in window);
  369.     const out = { ok:true, present: has };
  370.     console.log('Result:', out);
  371.     end(); return out;
  372.   }
  373.   // 12) PushManager presence
  374.   function testPushManager() {
  375.     const end = logG('Test: PushManager presence');
  376.     const has = 'PushManager' in window;
  377.     const out = { ok:true, present: has };
  378.     console.log('Result:', out);
  379.     end(); return out;
  380.   }
  381.   // 13) Permissions API (notifications state)
  382.   async function testPermissionsNotifications() {
  383.     const end = logG('Test: Permissions API (notifications)');
  384.     if (!(navigator.permissions?.query)) {
  385.       console.info('API not available');
  386.       end(); return { ok:false, note:'no-api' };
  387.     }
  388.     try {
  389.       const st = await navigator.permissions.query({ name: 'notifications' });
  390.       const out = { ok:true, state: st.state }; // 'granted'|'denied'|'prompt'
  391.       console.log('Result:', out);
  392.       end(); return out;
  393.     } catch (e) {
  394.       console.warn('Error:', e?.name || e);
  395.       end(); return { ok:false, note:'error' };
  396.     }
  397.   }
  398.   // 14) cookieStore presence
  399.   function testCookieStore() {
  400.     const end = logG('Test: cookieStore presence');
  401.     const has = 'cookieStore' in window;
  402.     const out = { ok:true, present: has };
  403.     console.log('Result:', out);
  404.     end(); return out;
  405.   }
  406.   // 15) navigator.userAgentData presence (Chromium)
  407.   function testUserAgentData() {
  408.     const end = logG('Test: navigator.userAgentData presence');
  409.     const d = (navigator).userAgentData;
  410.     const out = { ok:true, present: !!d, brands: d?.brands || null, mobile: d?.mobile ?? null };
  411.     console.log('Result:', out);
  412.     end(); return out;
  413.   }
  414.   // =====================
  415.   // Run sequentially
  416.   // =====================
  417.   const results = {};
  418.   results.storageEstimate         = await testStorageEstimate();
  419.   results.webkitTemporaryQuota    = await testWebkitTemporaryQuota();
  420.   results.persisted               = await testPersisted();
  421.   results.idbDatabases            = await testIndexedDBDatabases();
  422.   results.idbWriteProbe           = await testIndexedDBWriteProbe();
  423.   results.serviceWorkerRegister   = await testServiceWorkerRegister('/sw-probe.js');
  424.   results.cacheStorage            = await testCacheStorage();
  425.   results.cookies                 =        testCookies();
  426.   results.opfs                    = await testOPFS();
  427.   results.storageBuckets          = await testStorageBuckets();
  428.   results.fsAccessPickers         =        testFileSystemAccessPickers();
  429.   results.pushManager             =        testPushManager();
  430.   results.permissionsNotifications= await testPermissionsNotifications();
  431.   results.cookieStore             =        testCookieStore();
  432.   results.userAgentData           =        testUserAgentData();
  433.   console.group('All test results');
  434.   console.dir(results);
  435.   console.groupEnd();
  436.   // Return the object for convenience
  437.   results;
  438. })();
  439. */
  440. </script>
  441. {% endblock %}