1(function(){ 2 'use strict'; 3 if(window.fmhAdapterBound)return; 4 window.fmhAdapterBound=true; 5 const selector='form[data-fmh-adapter],form[data-fmh-inbox]'; 6 document.querySelectorAll(selector).forEach(form=>{form.noValidate=true;}); 7 function status(form,text){ 8 let box=form.querySelector('[role="status"]'); 9 if(!box){box=document.createElement('p');box.setAttribute('role','status');box.setAttribute('aria-live','polite');form.appendChild(box);} 10 box.textContent=text; 11 } 12 function reveal(form){ 13 const invalid=form.querySelector(':invalid');if(!invalid)return; 14 let node=invalid.parentElement; 15 while(node && node!==form){ 16 if(node.hidden)node.hidden=false; 17 const css=getComputedStyle(node); 18 if(css.display==='none')node.style.setProperty('display','block','important'); 19 if(css.visibility==='hidden')node.style.setProperty('visibility','visible','important'); 20 node=node.parentElement; 21 } 22 if(invalid.reportValidity)invalid.reportValidity(); 23 if(invalid.focus)invalid.focus(); 24 } 25 async function send(form){ 26 if(form.dataset.fmhSending==='1' || form.dataset.fmhSent==='1')return; 27 form.noValidate=true; 28 if(!form.checkValidity()){reveal(form);status(form,'Vérifiez les champs obligatoires.');return;} 29 let bytes=0; 30 for(const input of form.querySelectorAll('input[type="file"]'))for(const file of input.files){ 31 bytes+=file.size; 32 const max=Math.min(10485760,Number(input.getAttribute('data-max-bytes'))||10485760); 33 if(file.size>max || bytes>15728640){status(form,'Fichier trop volumineux. Maximum 10 Mo par fichier et 15 Mo au total.');return;} 34 } 35 // Capture at click time: a delayed third-party initializer must not alter the payload during nonce refresh. 36 const data=new FormData(form); 37 form.dataset.fmhSending='1'; 38 const buttons=Array.from(form.querySelectorAll('button[type="submit"],button:not([type]),input[type="submit"]')); 39 const disabled=buttons.map(b=>b.disabled);buttons.forEach(b=>{b.disabled=true;}); 40 status(form,'Enregistrement en coursâ¦'); 41 try { 42 const fingerprint=JSON.stringify(Array.from(data.entries()).filter(([key])=>!['fmh_nonce','fmh_request_id','fmh_json'].includes(key)).map(([key,value])=>[key,typeof value==='string'?value:[value.name,value.size,value.lastModified]])); 43 const digest=Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256',new TextEncoder().encode(fingerprint)))).map(v=>v.toString(16).padStart(2,'0')).join(''); 44 const storageKey='fmh-intake:'+form.action+':'+digest; 45 let requestId=form.dataset.fmhPayloadHash===digest?form.dataset.fmhRequestId:null; 46 try {requestId=requestId||sessionStorage.getItem(storageKey);}catch(e){} 47 if(!requestId)requestId=Array.from(crypto.getRandomValues(new Uint8Array(16))).map(v=>v.toString(16).padStart(2,'0')).join(''); 48 form.dataset.fmhPayloadHash=digest;form.dataset.fmhRequestId=requestId; 49 try {sessionStorage.setItem(storageKey,requestId);}catch(e){} 50 data.set('fmh_request_id',requestId); 51 const nonceUrl=new URL(form.action,location.href);nonceUrl.searchParams.set('action','fmh_nonce'); 52 const freshResponse=await fetch(nonceUrl.toString(),{method:'POST',credentials:'same-origin',cache:'no-store'}); 53 if(!freshResponse.ok)throw new Error('Le formulaire ne répond pas. Réessayez.'); 54 const fresh=await freshResponse.json(); 55 if(fresh.ok!==true || typeof fresh.nonce!=='string' || !fresh.nonce)throw new Error('Le formulaire a expiré. Réessayez.'); 56 form.elements.fmh_nonce.value=fresh.nonce; 57 data.set('fmh_nonce',fresh.nonce);data.set('fmh_json','1'); 58 const response=await fetch(form.action,{method:'POST',credentials:'same-origin',cache:'no-store',body:data}); 59 const result=await response.json(); 60 if(!response.ok || result.ok!==true)throw new Error(result.message || "L'envoi a échoué. Votre saisie est conservée."); 61 form.dataset.fmhSent='1'; 62 status(form,result.success || 'Votre message a été enregistré pour vérification.'); 63 } catch(error){status(form,error.message || "L'envoi a échoué. Votre saisie est conservée.");} 64 finally {delete form.dataset.fmhSending;buttons.forEach((b,index)=>{b.disabled=form.dataset.fmhSent==='1' || disabled[index];});} 65 } 66 document.addEventListener('submit',function(event){ 67 const form=event.target;if(!form.matches || !form.matches(selector))return; 68 event.preventDefault();event.stopImmediatePropagation();return send(form); 69 },true); 70 document.addEventListener('click',function(event){ 71 const button=event.target.closest?event.target.closest('button,input'):null; 72 if(!button || button.type!=='submit' || !button.form || !button.form.matches(selector))return; 73 event.preventDefault();event.stopImmediatePropagation();return send(button.form); 74 },true); 75})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.