1/** 2 * Age Verification Widget - JavaScript universel 3 * Compatible avec tous types de sites web 4 */ 5(function(window, document) { 6 'use strict'; 7 8 // Ãviter les conflits si le widget est déjà chargé 9 if (window.AgeVerificationWidget) { 10 return; 11 } 12 13 var AgeVerificationWidget = { 14 // Configuration par défaut 15 config: { 16 apiUrl: '', 17 debug: false, 18 onVerified: null, 19 onDenied: null, 20 onError: null 21 }, 22 23 // Ãtat du widget 24 state: { 25 initialized: false, 26 popupVisible: false, 27 cameraStream: null, 28 verificationConfig: null 29 }, 30 31 32 33 /** 34 * Initialise le widget 35 */ 36 init: function(options) { 37 if (this.state.initialized) { 38 this.log('Widget déjà initialisé'); 39 return; 40 } 41 42 // Fusion des options 43 this.config = this.extend(this.config, options || {}); 44 45 if (!this.config.apiUrl) { 46 this.error('URL API requise pour initialiser le widget'); 47 return; 48 } 49 50 this.log('Initialisation du widget...'); 51 52 // Vérifier si on est en mode administration 53 if (window.AgeVerificationWidget_AdminMode === true) { 54 this.log('Mode administration détecté - pas de vérification automatique'); 55 this.state.initialized = true; 56 return; 57 } 58 59 // Vérification de la configuration d'abord 60 this.checkConfiguration(); 61 62 this.state.initialized = true; 63 }, 64 65 /** 66 * Vérifie la configuration côté serveur 67 */ 68 checkConfiguration: function() { 69 var self = this; 70 71 // ðª PREMIÃRE VÃRIFICATION: Cookie existant (avant même l'API) 72 var verifiedCookie = this.getCookie('avs_verified'); 73 this.log('ðª Vérification préalable du cookie avs_verified: "' + verifiedCookie + '"'); 74 75 if (verifiedCookie === '1') { 76 this.log('â Utilisateur DÃJà vérifié via cookie - AUCUNE protection ni popup'); 77 return; // Sortir complètement - pas besoin d'API 78 } 79 80 this.request('GET', '/api/config', null, function(response) { 81 if (response.show_popup) { 82 // Fusionner la configuration avec les textes multilingues 83 self.state.verificationConfig = self.mergeConfigWithTexts(response); 84 85 self.log('Configuration multilingue chargée. Langue détectée: ' + response.language); 86 if (response.detected_country) { 87 self.log('Pays détecté: ' + response.detected_country); 88 } 89 90 // ð Initialiser la protection du contenu seulement si popup requis 91 self.initContentProtection(); 92 93 self.showPopup(); 94 } else { 95 self.log('â Popup non requis pour cet utilisateur - AUCUNE PROTECTION'); 96 // Aucune protection du contenu nécessaire 97 } 98 }, function(error) { 99 self.error('Erreur lors de la vérification de configuration:', error); 100 // En cas d'erreur, ne pas activer la protection pour éviter de bloquer le site 101 self.log('â ï¸ Erreur de configuration - protection désactivée par sécurité'); 102 }); 103 }, 104 105 /** 106 * Charge la configuration spécifiquement pour affichage manuel du popup 107 */ 108 loadConfigurationForPopup: function() { 109 var self = this; 110 111 this.request('GET', '/api/config', null, function(response) { 112 // Toujours charger la configuration, même si show_popup est false 113 if (response.config) { 114 // Fusionner la configuration avec les textes multilingues 115 self.state.verificationConfig = self.mergeConfigWithTexts(response); 116 117 self.log('Configuration multilingue chargée pour affichage manuel. Langue: ' + response.language); 118 // Maintenant afficher le popup 119 self.showPopup(); 120 } else { 121 self.error('Configuration non disponible depuis l\'API'); 122 } 123 }, function(error) { 124 self.error('Erreur lors du chargement de la configuration:', error); 125 }); 126 }, 127 128 /** 129 * Fusionne la configuration avec les textes de la langue appropriée 130 */ 131 mergeConfigWithTexts: function(response) { 132 var config = response.config || {}; 133 var popupTexts = response.popup_texts || {}; 134 135 // Fusionner les textes multilingues dans la configuration principale 136 for (var key in popupTexts) { 137 if (popupTexts.hasOwnProperty(key)) { 138 config[key] = popupTexts[key]; 139 } 140 } 141 142 // Ajouter des informations sur la langue 143 config.detected_language = response.language || 'fr'; 144 config.detected_country = response.detected_country || null; 145 config.auto_language_detection = response.auto_language_detection || false; 146 147 this.log('ð Textes fusionnés pour la langue: ' + config.detected_language); 148 149 return config; 150 }, 151 152 /** 153 * Affiche le popup de vérification 154 */ 155 showPopup: function() { 156 if (this.state.popupVisible) { 157 return; 158 } 159 160 // Si pas de configuration, la récupérer d'abord 161 if (!this.state.verificationConfig) { 162 this.log('Récupération de la configuration pour affichage manuel du popup...'); 163 this.loadConfigurationForPopup(); 164 return; 165 } 166 167 this.log('Affichage du popup de vérification'); 168 169 this.createPopupHTML(); 170 this.setupEventListeners(); 171 this.state.popupVisible = true; 172 173 // Empêcher le scroll de la page 174 document.body.style.overflow = 'hidden'; 175 }, 176 177 /** 178 * Crée le HTML du popup 179 */ 180 createPopupHTML: function() { 181 var config = this.state.verificationConfig; 182 183 // Création du container principal 184 var modal = document.createElement('div'); 185 modal.id = 'avs-modal'; 186 187 // Ajouter la classe de thème si spécifiée 188 if (config.theme && config.theme !== 'default') { 189 modal.className = 'avs-theme-' + config.theme; 190 } 191 192 modal.innerHTML = this.getPopupHTML(config); 193 194 // Ajouter le CSS personnalisé si défini 195 if (config.custom_css && config.custom_css.trim()) { 196 this.injectCustomCSS(config.custom_css); 197 } 198 199 document.body.appendChild(modal); 200 }, 201 202 /** 203 * Génère le HTML du popup 204 */ 205 getPopupHTML: function(config) { 206 var borderRadius = config.popup_shape === 'square' ? '0px' : '12px'; 207 var buttonBorderRadius = config.rounded_buttons ? '999px' : '4px'; 208 209 return ` 210 <div id="avs-overlay" style=" 211 position: fixed; 212 top: 0; left: 0; right: 0; bottom: 0; 213 background: rgba(0,0,0,0.6); 214 backdrop-filter: blur(4px); 215 display: flex; 216 justify-content: center; 217 align-items: center; 218 z-index: 999999; 219 font-family: '${config.font_family}', Arial, sans-serif; 220 "> 221 <div id="avs-content" style=" 222 max-width: 420px; 223 width: 90vw; 224 background: ${config.popup_bg_color}; 225 color: ${config.popup_text_color}; 226 padding: 1.5rem; 227 border-radius: ${borderRadius}; 228 box-sizing: border-box; 229 max-height: 90vh; 230 overflow-y: auto; 231 font-size: ${config.font_size_desktop}px; 232 "> 233 <!-- Onglets --> 234 <div id="avs-tabs" style=" 235 display: flex; 236 border-bottom: 2px solid ${config.popup_button_color}; 237 margin-bottom: 1rem; 238 "> 239 <button type="button" id="avs-tab-verify" class="avs-tab active" style=" 240 flex: 1; 241 padding: 0.7rem 0; 242 font-size: 1.1rem; 243 border: none; 244 background: none; 245 cursor: pointer; 246 border-bottom: 3px solid ${config.popup_button_color}; 247 color: ${config.popup_button_color}; 248 font-weight: bold; 249 ">${config.tab_verify_title}</button> 250 </div> 251 252 <!-- Contenu vérification --> 253 <div id="avs-verify-content"> 254 <h2 id="avs-title">${config.tab_verify_title}</h2> 255 <p id="avs-intro">${config.intro_text}</p> 256 257 <button id="avs-start-btn" style=" 258 width: 100%; 259 margin-top: 1rem; 260 padding: 0.6rem 1.2rem; 261 font-size: 1.1rem; 262 background-color: ${config.popup_button_color}; 263 color: white; 264 border: none; 265 border-radius: ${buttonBorderRadius}; 266 cursor: pointer; 267 margin-bottom: 1.5rem; 268 ">${config.start_button_text}</button> 269 270 <div id="avs-camera-container" style="display: none;"> 271 <p id="avs-camera-instructions">${config.camera_instructions}</p> 272 273 <div style="position: relative; width: 100%; margin-bottom: 1rem;"> 274 <video id="avs-video" autoplay playsinline style=" 275 width: 100%; 276 border-radius: 12px; 277 "></video> 278 <div id="avs-overlay-guide" style=" 279 position: absolute; 280 border: 3px solid rgba(0, 123, 255, 0.8); 281 border-radius: 50% / 60%; 282 width: 70%; 283 height: 80%; 284 top: 50%; 285 left: 50%; 286 transform: translate(-50%, -50%); 287 pointer-events: none; 288 box-sizing: border-box; 289 "></div> 290 </div> 291 292 <button id="avs-capture-btn" style=" 293 width: 100%; 294 padding: 0.6rem 1.2rem; 295 font-size: 1.1rem; 296 background-color: ${config.popup_button_color}; 297 color: white; 298 border: none; 299 border-radius: ${buttonBorderRadius}; 300 cursor: pointer; 301 ">${config.capture_button_text}</button> 302 </div> 303 304 <div id="avs-result" style=" 305 text-align: center; 306 font-weight: bold; 307 margin-top: 1rem; 308 min-height: 1.5em; 309 "></div> 310 311 <div id="avs-access-denied" style=" 312 display: none; 313 color: red; 314 text-align: center; 315 font-weight: bold; 316 margin-top: 1rem; 317 ">
317${config.access_denied_text}</div> 318 319 <p style="margin-top: 1rem; text-align: center; font-size: 0.85rem;"> 320 <a href="#" id="avs-privacy-link" style="color: ${config.popup_button_color};"> 321 ${config.privacy_link_text} 322 </a> 323 </p> 324 325 ${config.login_link_enabled ? ` 326 <div style="text-align: center; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid rgba(128,128,128,0.3);"> 327 <a href="${config.login_link_url}" target="${config.login_link_target}" 328 style=" 329 display: inline-block; 330 padding: 0.5rem 1rem; 331 background-color: transparent; 332 color: ${config.popup_button_color}; 333 text-decoration: none; 334 border: 2px solid ${config.popup_button_color}; 335 border-radius: ${buttonBorderRadius}; 336 font-weight: bold; 337 transition: all 0.3s ease; 338 " 339 onmouseover="this.style.backgroundColor='${config.popup_button_color}'; this.style.color='white';" 340 onmouseout="this.style.backgroundColor='transparent'; this.style.color='${config.popup_button_color}';" 341 >${config.login_link_text}</a> 342 </div> 343 ` : ''} 344 </div> 345 </div> 346 </div> 347 348 <!-- Modal de confidentialité --> 349 <div id="avs-privacy-modal" style=" 350 display: none; 351 position: fixed; 352 z-index: 1000000; 353 top: 0; left: 0; 354 width: 100%; height: 100%; 355 background: rgba(0,0,0,0.6); 356 backdrop-filter: blur(4px); 357 "> 358 <div style=" 359 background: white; 360 max-width: 500px; 361 margin: 5% auto; 362 padding: 1.5rem; 363 border-radius: 10px; 364 font-size: 0.9rem; 365 overflow-y: auto; 366 max-height: 90%; 367 "> 368 <h3 style="margin-top: 0;">${config.privacy_modal_title}</h3> 369 <div>${config.privacy_text}</div> 370 <button id="avs-close-privacy" style=" 371 margin-top: 1rem; 372 padding: 0.5rem 1rem; 373 border: none; 374 background: ${config.popup_button_color}; 375 color: white; 376 border-radius: 5px; 377 cursor: pointer; 378 ">Fermer</button> 379 </div> 380 </div> 381 382 <canvas id="avs-canvas" style="display: none;"></canvas> 383 `; 384 }, 385 386 /** 387 * Configure les événements 388 */ 389 setupEventListeners: function() { 390 var self = this; 391 392 // Bouton démarrer 393 document.getElementById('avs-start-btn').addEventListener('click', function() { 394 self.startCamera(); 395 }); 396 397 // Bouton capturer 398 document.getElementById('avs-capture-btn').addEventListener('click', function() { 399 self.capturePhoto(); 400 }); 401 402 // Lien confidentialité 403 document.getElementById('avs-privacy-link').addEventListener('click', function(e) { 404 e.preventDefault(); 405 document.getElementById('avs-privacy-modal').style.display = 'block'; 406 }); 407 408 // Fermer modal confidentialité 409 document.getElementById('avs-close-privacy').addEventListener('click', function() { 410 document.getElementById('avs-privacy-modal').style.display = 'none'; 411 }); 412 }, 413 414 /** 415 * Démarre la caméra 416 */ 417 startCamera: function() { 418 var self = this; 419 var startBtn = document.getElementById('avs-start-btn'); 420 var cameraContainer = document.getElementById('avs-camera-container'); 421 var video = document.getElementById('avs-video'); 422 423 startBtn.disabled = true; 424 startBtn.textContent = this.state.verificationConfig.loading_text || 'Démarrage...'; 425 426 navigator.mediaDevices.getUserMedia({ 427 video: { facingMode: 'user' } 428 }).then(function(stream) { 429 self.state.cameraStream = stream; 430 video.srcObject = stream; 431 432 // Afficher la caméra et masquer le bouton 433 cameraContainer.style.display = 'block'; 434 startBtn.style.display = 'none'; 435 436 // Masquer titre et intro si configuré 437 if (self.state.verificationConfig.hide_intro_on_start) { 438 document.getElementById('avs-title').style.display = 'none'; 439 document.getElementById('avs-intro').style.display = 'none'; 440 } 441 442 }).catch(function(error) { 443 self.error('Erreur accès caméra:', error); 444 startBtn.disabled = false;
445 startBtn.textContent = self.state.verificationConfig.start_button_text; 446 447 var errorMsg = self.state.verificationConfig.camera_error_text || 448 'Erreur lors de l\'accès à la caméra'; 449 alert(errorMsg); 450 }); 451 }, 452 453 /** 454 * Capture une photo 455 */ 456 capturePhoto: function() { 457 var self = this; 458 var video = document.getElementById('avs-video'); 459 var canvas = document.getElementById('avs-canvas'); 460 var captureBtn = document.getElementById('avs-capture-btn'); 461 var resultDiv = document.getElementById('avs-result'); 462 var accessDeniedDiv = document.getElementById('avs-access-denied'); 463 464 captureBtn.disabled = true; 465 resultDiv.textContent = 'Analyse en cours...'; 466 accessDeniedDiv.style.display = 'none'; 467 468 // Capture de l'image 469 canvas.width = video.videoWidth; 470 canvas.height = video.videoHeight; 471 var ctx = canvas.getContext('2d'); 472 ctx.drawImage(video, 0, 0, canvas.width, canvas.height); 473 474 var base64 = canvas.toDataURL('image/jpeg'); 475 476 // Envoi pour analyse 477 this.request('POST', '/api/analyze', { 478 photo_base64: base64 479 }, function(response) { 480 if (response.success && response.verified) { 481 // Succès 482 resultDiv.style.color = 'green'; 483 resultDiv.textContent = self.state.verificationConfig.success_message; 484 485 // Cookie de vérification 486 // Succès - marquer comme vérifié avec la durée configurée 487 var duration = self.state.verificationConfig.verification_duration_days || 30; 488 self.setCookie('avs_verified', '1', duration); 489 490 // ð Révéler le contenu et arrêter la surveillance 491 self.showContent(); 492 self.stopDOMSurveillance(); 493 494 // Callback 495 if (self.config.onVerified) { 496 self.config.onVerified(response.age); 497 } 498 499 // Fermeture automatique 500 setTimeout(function() { 501 self.hidePopup(); 502 }, (self.state.verificationConfig.success_delay || 2) * 1000); 503 504 } else if (response.success && !response.verified) { 505 // Ãge insuffisant 506 accessDeniedDiv.style.display = 'block'; 507 resultDiv.textContent = ''; 508 509 if (self.config.onDenied) { 510 self.config.onDenied(response.age); 511 } 512 513 } else { 514 // Erreur 515 resultDiv.textContent = response.error || 'Erreur lors de l\'analyse'; 516 } 517 518 captureBtn.disabled = false; 519 520 }, function(error) { 521 resultDiv.textContent = 'Erreur: ' + error; 522 captureBtn.disabled = false; 523 524 if (self.config.onError) { 525 self.config.onError(error); 526 } 527 }); 528 }, 529 530 /** 531 * Masque le popup 532 */ 533 hidePopup: function() { 534 if (!this.state.popupVisible) { 535 return; 536 } 537 538 // Arrêt de la caméra 539 if (this.state.cameraStream) { 540 this.state.cameraStream.getTracks().forEach(function(track) { 541 track.stop(); 542 }); 543 this.state.cameraStream = null; 544 } 545 546 // Suppression du DOM 547 var modal = document.getElementById('avs-modal'); 548 if (modal) { 549 modal.remove(); 550 } 551 552 // Suppression du CSS personnalisé 553 var customStyleTag = document.getElementById('avs-custom-styles'); 554 if (customStyleTag) { 555 customStyleTag.remove(); 556 } 557 558 // Restauration du scroll 559 document.body.style.overflow = ''; 560 561 this.state.popupVisible = false;
562 563 this.log('Popup masqué'); 564 }, 565 566 /** 567 * Effectue une requête HTTP 568 */ 569 request: function(method, endpoint, data, success, error) { 570 var xhr = new XMLHttpRequest(); 571 var url = this.config.apiUrl + endpoint; 572 573 xhr.open(method, url, true); 574 xhr.setRequestHeader('Content-Type', 'application/json'); 575 576 xhr.onreadystatechange = function() { 577 if (xhr.readyState === 4) { 578 try { 579 var response = JSON.parse(xhr.responseText); 580 581 if (xhr.status >= 200 && xhr.status < 300) { 582 if (success) success(response); 583 } else { 584 if (error) error(response.error || 'Erreur HTTP ' + xhr.status); 585 } 586 } catch (e) { 587 if (error) error('Erreur de parsing JSON'); 588 } 589 } 590 }; 591 592 xhr.onerror = function() { 593 if (error) error('Erreur de réseau'); 594 }; 595 596 if (data) { 597 xhr.send(JSON.stringify(data)); 598 } else { 599 xhr.send(); 600 } 601 }, 602 603 /** 604 * Définit un cookie 605 */ 606 setCookie: function(name, value, days) { 607 var expires = ''; 608 if (days) { 609 var date = new Date(); 610 date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 611 expires = '; expires=' + date.toUTCString(); 612 } 613 document.cookie = name + '=' + (value || '') + expires + '; path=/'; 614 }, 615 616 /** 617 * Fusion d'objets 618 */ 619 extend: function(target, source) { 620 var result = {}; 621 for (var key in target) { 622 if (target.hasOwnProperty(key)) { 623 result[key] = target[key]; 624 } 625 } 626 for (var key in source) { 627 if (source.hasOwnProperty(key)) { 628 result[key] = source[key]; 629 } 630 } 631 return result; 632 }, 633 634 /** 635 * Log de débogage 636 */ 637 log: function() { 638 if (this.config.debug && console && console.log) { 639 console.log.apply(console, ['[AgeVerificationWidget]'].concat(Array.prototype.slice.call(arguments))); 640 } 641 }, 642 643 /** 644 * ð Initialise la protection du contenu 645 */ 646 initContentProtection: function() { 647 var self = this; 648 649 // Vérifier que le DOM est prêt avant toute manipulation 650 if (!document.body) { 651 this.log('â ï¸ DOM non prêt - report de l\'initialisation de la protection'); 652 setTimeout(function() { 653 self.initContentProtection(); 654 }, 100); 655 return; 656 } 657 658 // ð Protection du contenu (cookie déjà vérifié en amont) 659 this.log('ð Activation de la protection du contenu - utilisateur non vérifié'); 660 661 // Masquer le contenu immédiatement 662 this.hideContent(); 663 664 // Démarrer la surveillance DOM 665 this.startDOMSurveillance(); 666 667 // Protection contre l'inspection (optionnel) 668 this.addInspectionProtection(); 669 670 this.log('Protection du contenu initialisée'); 671 }, 672 673 /** 674 * ð Masque le contenu de la page 675 */ 676 hideContent: function() { 677 // Vérifier que le DOM est prêt 678 if (!document.body) { 679 this.log('â ï¸ DOM non prêt - report du masquage du contenu'); 680 var self = this; 681 setTimeout(function() { 682 self.hideContent(); 683 }, 100); 684 return; 685 } 686 687 document.body.classList.add('avs-content-hidden'); 688 689 // Note: Le contenu est maintenant flouté (pas caché), pas besoin de message de protection systématique 690 691 this.log('Contenu flouté - vérification d\'âge requise'); 692 }, 693 694 /** 695 * ð Révèle le contenu après vérification 696 */ 697 showContent: function() { 698 // Vérifier que le DOM est prêt 699 if (!document.body) { 700 this.log('â ï¸ DOM non prêt - report de la révélation du contenu'); 701 var self = this; 702 setTimeout(function() { 703 self.showContent(); 704 }, 100); 705 return; 706 } 707 708 document.body.classList.remove('avs-content-hidden'); 709 710 // Supprimer le message de protection 711 this.hideProtectionMessage(); 712 713 this.log('Contenu révélé - vérification réussie'); 714 }, 715 716 /** 717 * ð Affiche un message de protection 718 */ 719 showProtectionMessage: function() { 720 // Vérifier que le DOM est prêt 721 if (!document.body) { 722 this.log('â ï¸ DOM non prêt - report de l\'affichage du message de protection'); 723 var self = this; 724 setTimeout(function() { 725 self.showProtectionMessage(); 726 }, 100); 727 return; 728 } 729 730 // Ãviter de créer plusieurs messages 731 if (document.getElementById('avs-protection-overlay')) { 732 return; 733 } 734
735 var overlay = document.createElement('div'); 736 overlay.id = 'avs-protection-overlay'; 737 overlay.className = 'avs-protection-overlay'; 738 739 overlay.innerHTML = 740 '<div class="avs-protection-message">' + 741 '<h2 style="margin-bottom: 15px; font-size: 24px;">ð Contenu Protégé</h2>' + 742 '<p style="margin-bottom: 15px; opacity: 0.9;">Ce site contient du contenu réservé aux adultes.</p>' + 743 '<p style="margin-bottom: 20px; opacity: 0.8;">Veuillez compléter la vérification d\'âge pour accéder au contenu.</p>' + 744 '<div style="animation: pulse 2s infinite;">â³ Chargement de la vérification...</div>' + 745 '</div>'; 746 747 document.body.appendChild(overlay); 748 }, 749 750 /** 751 * ð Masque le message de protection 752 */ 753 hideProtectionMessage: function() { 754 var overlay = document.getElementById('avs-protection-overlay'); 755 if (overlay) { 756 overlay.remove(); 757 } 758 }, 759 760 /** 761 * ð Surveillance DOM pour détecter la suppression du modal 762 */ 763 startDOMSurveillance: function() { 764 var self = this; 765 766 // Observer pour mutations DOM 767 if (window.MutationObserver) { 768 this.domObserver = new MutationObserver(function(mutations) { 769 // Vérifier si le modal a été supprimé 770 if (!document.getElementById('avs-modal') && 771 self.state.popupVisible && 772 self.getCookie('avs_verified') !== '1') { 773 774 self.log('â ï¸ Tentative de suppression du modal détectée !'); 775 self.handleSecurityBreach(); 776 } 777 }); 778 779 this.domObserver.observe(document.body, { 780 childList: true, 781 subtree: true 782 }); 783 } 784 785 // Surveillance par intervalle (fallback) 786 this.surveillanceInterval = setInterval(function() { 787 if (self.state.popupVisible && 788 !document.getElementById('avs-modal') && 789 self.getCookie('avs_verified') !== '1') { 790 791 self.log('â ï¸ Modal manquant détecté par surveillance !'); 792 self.handleSecurityBreach(); 793 } 794 }, 1000); 795 }, 796 797 /** 798 * ð Arrête la surveillance DOM 799 */ 800 stopDOMSurveillance: function() { 801 if (this.domObserver) { 802 this.domObserver.disconnect(); 803 this.domObserver = null; 804 } 805 806 if (this.surveillanceInterval) { 807 clearInterval(this.surveillanceInterval); 808 this.surveillanceInterval = null; 809 } 810 }, 811 812 /** 813 * ð Gestion des tentatives de contournement 814 */ 815 handleSecurityBreach: function() { 816 // Masquer le contenu immédiatement 817 this.hideContent(); 818 819 // Afficher le message de protection seulement en cas de violation 820 this.showProtectionMessage(); 821 822 // Recréer le popup 823 this.showPopup(); 824 825 // Message d'avertissement (optionnel) 826 this.log('ð¨ Breach de sécurité - rechargement de la vérification'); 827 828 // Optionnel: Rediriger ou recharger la page 829 // setTimeout(function() { location.reload(); }, 2000); 830 }, 831 832 /** 833 * ð Protection contre l'inspection (optionnel) 834 */ 835 addInspectionProtection: function() { 836 // Désactiver le clic droit 837 document.addEventListener('contextmenu', function(e) { 838 e.preventDefault(); 839 return false;
840 }); 841 842 // Désactiver les raccourcis clavier de développement 843 document.addEventListener('keydown', function(e) { 844 // F12, Ctrl+Shift+I, Ctrl+Shift+J, Ctrl+U 845 if (e.key === 'F12' || 846 (e.ctrlKey && e.shiftKey && (e.key === 'I' || e.key === 'J')) || 847 (e.ctrlKey && e.key === 'U')) { 848 e.preventDefault(); 849 return false; 850 } 851 }); 852 853 // Détecter l'ouverture des outils de développement 854 var devtools = {open: false, orientation: null}; 855 var threshold = 160; 856 857 setInterval(function() { 858 if (window.outerHeight - window.innerHeight > threshold || 859 window.outerWidth - window.innerWidth > threshold) { 860 if (!devtools.open) { 861 devtools.open = true; 862 // Optionnel: Action lors de l'ouverture des devtools 863 console.clear(); 864 } 865 } else { 866 devtools.open = false; 867 } 868 }, 500); 869 }, 870 871 /** 872 * Injecte du CSS personnalisé dans la page 873 */ 874 injectCustomCSS: function(customCSS) { 875 // Vérifier si un style tag pour le widget existe déjà 876 var existingStyleTag = document.getElementById('avs-custom-styles'); 877 878 if (existingStyleTag) { 879 // Mettre à jour le CSS existant 880 existingStyleTag.textContent = customCSS; 881 } else { 882 // Créer un nouveau style tag 883 var styleTag = document.createElement('style'); 884 styleTag.id = 'avs-custom-styles'; 885 styleTag.type = 'text/css'; 886 styleTag.textContent = customCSS; 887 888 // Ajouter au head du document 889 document.head.appendChild(styleTag); 890 } 891 892 this.log('CSS personnalisé injecté:', customCSS.length + ' caractères'); 893 }, 894 895 /** 896 * ðª Définit un cookie 897 */ 898 setCookie: function(name, value, days) { 899 var expires = ""; 900 if (days) { 901 var date = new Date(); 902 date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 903 expires = "; expires=" + date.toUTCString(); 904 } 905 906 // Déterminer le chemin optimal pour le cookie 907 var path = this.getCookiePath(); 908 909 // Créer le cookie avec tous les paramètres nécessaires 910 var cookieString = name + "=" + (value || "") + expires + 911 "; path=" + path + 912 "; SameSite=Lax"; 913 914 // Ajouter le domaine si on n'est pas sur localhost 915 if (window.location.hostname !== 'localhost' && 916 window.location.hostname !== '127.0.0.1' && 917 !window.location.hostname.match(/^\d+\.\d+\.\d+\.\d+$/)) { 918 cookieString += "; domain=" + window.location.hostname; 919 } 920 921 document.cookie = cookieString; 922 923 this.log('ðª Cookie défini: ' + name + '=' + value + ' [path=' + path + ']'); 924 }, 925 926 /** 927 * ðª Récupère un cookie 928 */ 929 getCookie: function(name) { 930 var nameEQ = name + "="; 931 var ca = document.cookie.split(';'); 932 for (var i = 0; i < ca.length; i++) { 933 var c = ca[i]; 934 while (c.charAt(0) === ' ') { 935 c = c.substring(1, c.length); 936 } 937 if (c.indexOf(nameEQ) === 0) { 938 return c.substring(nameEQ.length, c.length); 939 } 940 } 941 return null; 942 }, 943 944 /** 945 * ðª Détermine le chemin optimal pour les cookies 946 */ 947 getCookiePath: function() { 948 // Si on est à la racine, utiliser / 949 if (window.location.pathname === '/' || window.location.pathname === '') { 950 return '/'; 951 } 952 953 // Si on est dans un sous-dossier, utiliser le chemin du dossier parent 954 var pathParts = window.location.pathname.split('/'); 955 956 // Enlever la dernière partie (nom du fichier) 957 pathParts.pop(); 958 959 // Si il reste des parties, joindre avec / et ajouter / à la fin 960 if (pathParts.length > 1) { 961 return pathParts.join('/') + '/'; 962 } 963 964 // Par défaut, utiliser / 965 return '/'; 966 }, 967 968 /** 969 * ðª Supprime un cookie 970 */ 971 eraseCookie: function(name) { 972 var path = this.getCookiePath(); 973 974 // Supprimer avec différents chemins pour être sûr 975 var pathsToTry = ['/', path]; 976
977 pathsToTry.forEach(function(p) { 978 document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=' + p + ';'; 979 }); 980 981 this.log('ðï¸ Cookie supprimé: ' + name); 982 }, 983 984 /** 985 * Log d'erreur 986 */ 987 error: function() { 988 if (console && console.error) { 989 console.error.apply(console, ['[AgeVerificationWidget]'].concat(Array.prototype.slice.call(arguments))); 990 } 991 } 992 }; 993 994 // Exposition globale 995 window.AgeVerificationWidget = AgeVerificationWidget; 996 997})(window, document);
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.