1(function () { 2 'use strict'; 3 4 if (window.__contestioMagento2ScriptActive) { 5 console.log('contestio.js already initialised, skipping duplicate execution'); 6 return; 7 } 8 window.__contestioMagento2ScriptActive = true; 9 10 console.log('contestio.js loaded'); 11 12 const logger = { 13 log: function (message, data) { 14 console.log('Contestio - ' + message, data ?? ''); 15 }, 16 warn: function (message, data) { 17 console.warn('Contestio - ' + message, data ?? ''); 18 }, 19 error: function (message, data) { 20 console.error('Contestio - ' + message, data ?? ''); 21 } 22 }; 23 24 let iframeLoaded = false; 25 let pendingNavigationActions = []; 26 let navigationFlushTimeout = null; 27 let awaitingAck = null; 28 let awaitingAckTimeout = null; 29 const ACK_TIMEOUT_MS = 2000; 30 let lastAckPath = null; 31 const allowedIframeOrigins = new Set(); 32 33 if (typeof window !== "undefined" && window.location && window.location.origin) { 34 allowedIframeOrigins.add(window.location.origin); 35 } 36 37 window.__contestioAllowedOrigins = allowedIframeOrigins; 38 39 function scheduleNavigationFlush(delay = 50) { 40 if (navigationFlushTimeout) { 41 return; 42 } 43 44 navigationFlushTimeout = setTimeout(() => { 45 navigationFlushTimeout = null; 46 if (iframeLoaded) { 47 flushPendingNavigationActions(); 48 } else if (pendingNavigationActions.length) { 49 scheduleNavigationFlush(Math.min(delay * 2, 500)); 50 } 51 }, delay); 52 } 53 54 function normalizePathValue(pathname) { 55 if (!pathname || pathname === '/' || pathname === '') { 56 return '/'; 57 } 58 59 return pathname.startsWith('/') ? pathname : '/' + pathname; 60 } 61 62 function normalizeOrigin(url) { 63 if (!url || url === 'about:blank' || url.startsWith('about:')) return null; 64 try { 65 const origin = new URL(url).origin; 66 if (origin && origin !== 'null') { 67 return origin; 68 } 69 } catch (error) { 70 logger.warn('contestio.js - unable to normalize origin from url', url, error); 71 } 72 return null; 73 } 74 75 function queueNavigationAction(action, options = {}) { 76 if ( 77 !pendingNavigationActions.some( 78 (item) => 79 item.action === action && 80 JSON.stringify(item.options) === JSON.stringify(options) 81 ) 82 ) { 83 pendingNavigationActions.push({ action, options }); 84 logger.log('contestio.js - queue navigation action', { 85 action, 86 options, 87 queueLength: pendingNavigationActions.length, 88 }); 89 } 90 scheduleNavigationFlush(); 91 } 92 93 function flushPendingNavigationActions() { 94 if (!pendingNavigationActions.length) { 95 logger.log('contestio.js - flush skip (queue empty)'); 96 return; 97 } 98 99 if (navigationFlushTimeout) { 100 clearTimeout(navigationFlushTimeout); 101 navigationFlushTimeout = null; 102 } 103 104 const actions = pendingNavigationActions.slice(); 105 pendingNavigationActions = []; 106 107 actions.forEach(({ action, options }) => { 108 logger.log('contestio.js - flush action', { action, options }); 109 sendNavigationUpdateToIframe(action, { ...options, force: true }); 110 }); 111 } 112 113 function clearAwaitingAckTimeout() { 114 if (awaitingAckTimeout) { 115 clearTimeout(awaitingAckTimeout); 116 awaitingAckTimeout = null; 117 } 118 } 119 120 function setAwaitingAck(pathname, action, notifyAfterAck) { 121 const normalized = normalizePathValue(pathname); 122 awaitingAck = { 123 path: normalized, 124 action, 125 notifyAfterAck: Boolean(notifyAfterAck), 126 }; 127 128 // awaiting ack-path (verbose log removed) 129 clearAwaitingAckTimeout(); 130 131 if (lastAckPath && lastAckPath === normalized) { 132 completeAwaitingAck(); 133 return; 134 } 135 136 if (awaitingAck) { 137 awaitingAckTimeout = setTimeout(() => { 138 logger.warn('contestio.js - ack timeout, forcing completion', { 139 awaitingAck, 140 }); 141 const pending = awaitingAck; 142 awaitingAck = null; 143 awaitingAckTimeout = null; 144 if (pending && pending.action) { 145 sendNavigationUpdateToIframe(pending.action, { 146 force: true, 147 skipAckTracking: true, 148 }); 149 } 150 }, ACK_TIMEOUT_MS); 151 } 152 } 153 154 function completeAwaitingAck() { 155 if (!awaitingAck) { 156 return; 157 } 158 159 const pending = awaitingAck; 160 awaitingAck = null; 161 clearAwaitingAckTimeout(); 162 163 // ack-path resolved (verbose log removed) 164 165 if (pending.notifyAfterAck) { 166 sendNavigationUpdateToIframe(pending.action || 'replace', { 167 force: true, 168 skipAckTracking: true, 169 }); 170 } 171 } 172 173 function handleAckFromIframe(pathname) { 174 const normalized = normalizePathValue(pathname); 175 lastAckPath = normalized; 176 // handle ack-path (verbose log removed) 177 178 if (awaitingAck && awaitingAck.path === normalized) { 179 completeAwaitingAck(); 180 } 181 } 182 183 class KeyboardManager { 184 constructor(iframe) { 185 this.iframe = iframe;
186 this.lastHeight = window.visualViewport?.height || window.innerHeight; 187 188 if (window.visualViewport) { 189 window.visualViewport.addEventListener('resize', this.handleViewportResize.bind(this)); 190 } 191 } 192 193 handleViewportResize(event) { 194 const currentHeight = window.visualViewport.height; 195 const heightDiff = Math.abs(this.lastHeight - currentHeight); 196 197 // Scroll to the top if the height change 198 if (heightDiff > 20 && currentHeight > this.lastHeight) { 199 window.scrollTo({ 200 top: 0, 201 behavior: 'smooth' 202 }); 203 } 204 205 this.lastHeight = currentHeight; 206 } 207 } 208 209 function getContestioIframe() { 210 return document.querySelector('.contestio-iframe'); 211 } 212 213 function getIframeOrigin(iframe) { 214 if (!iframe) return null; 215 216 return normalizeOrigin(iframe.src); 217 } 218 219 function getParentPathname() { 220 try { 221 const urlParams = new URLSearchParams(window.location.search); 222 const l = urlParams.get('l'); 223 224 if (!l || l === '/' || l === '') { 225 return '/'; 226 } 227 228 return l.startsWith('/') ? l : '/' + l; 229 } catch (error) { 230 logger.warn('contestio.js - unable to read parent pathname', error); 231 return '/'; 232 } 233 } 234 235 function sendNavigationUpdateToIframe(action = 'replace', options = {}) { 236 const { 237 force = false, 238 trackAck = false, 239 notifyAfterAck = false, 240 skipAckTracking = false, 241 } = options; 242 243 const iframe = getContestioIframe(); 244 245 if (!iframe || !iframe.contentWindow) { 246 logger.warn('contestio.js - iframe not ready for navigation sync'); 247 queueNavigationAction(action, { 248 trackAck: trackAck && !skipAckTracking, 249 notifyAfterAck, 250 skipAckTracking, 251 }); 252 return; 253 } 254 255 if (!iframeLoaded && !force) { 256 queueNavigationAction(action, { 257 trackAck: trackAck && !skipAckTracking, 258 notifyAfterAck, 259 skipAckTracking, 260 }); 261 return; 262 } 263 264 const iframeOrigin = getIframeOrigin(iframe); 265 if (!iframeOrigin) return; 266 allowedIframeOrigins.add(iframeOrigin); 267 268 const pathname = getParentPathname(); 269 270 const message = { 271 type: 'parent-navigation', 272 action, 273 pathname, 274 title: document.title, 275 timestamp: Date.now() 276 }; 277 278 try { 279 // send navigation update (verbose log removed) 280 iframe.contentWindow.postMessage(message, iframeOrigin); 281 iframeLoaded = true; 282 iframe.dataset.contestioIframeLoaded = 'true'; 283 if (trackAck && !skipAckTracking) { 284 setAwaitingAck(pathname, action, notifyAfterAck); 285 } 286 } catch (error) { 287 logger.error('Error sending navigation update to iframe:', error); 288 queueNavigationAction(action, { 289 trackAck: trackAck && !skipAckTracking, 290 notifyAfterAck, 291 skipAckTracking, 292 }); 293 scheduleNavigationFlush(100); 294 } 295 } 296 297 function handleParentPopstate() { 298 sendNavigationUpdateToIframe('popstate', { 299 trackAck: true, 300 notifyAfterAck: false, 301 }); 302 } 303 304 function init() { 305 const startTime = Date.now(); 306 const isSafari = /Safari/.test(navigator.userAgent) && /iPhone|iPad/.test(navigator.userAgent); 307 308 logger.log(`init starting [${Date.now() - startTime}ms] - Safari iOS: ${isSafari}`); 309 310 // Attendre que l'iframe soit prête 311 const waitForIframe = () => { 312 const contestioIframe = document.getElementById('contestio-iframe'); 313 if (!contestioIframe) { 314 logger.warn('contestio.js - no iframe element found, aborting initialisation loop'); 315 return; 316 } 317 318 if (!window.contestioGlobal || !window.contestioGlobal.iframeReady) { 319 logger.log(`waiting for iframe to be ready [${Date.now() - startTime}ms]`); 320 321 // Ajouter à la queue des callbacks 322 if (window.contestioGlobal) { 323 window.contestioGlobal.callbacks.push(initContestioFeatures); 324 } 325 326 // Fallback: réessayer dans 500ms pour IOS 327 setTimeout(waitForIframe, 500); 328 return; 329 } 330 331 initContestioFeatures(); 332 }; 333
334 const initContestioFeatures = () => { 335 if (window.contestioInitialized) { 336 logger.log(`already initialized [${Date.now() - startTime}ms]`); 337 return; 338 } 339 340 window.contestioInitialized = true; 341 logger.log(`initializing features [${Date.now() - startTime}ms]`); 342 343 const container = document.querySelector('.contestio-container'); 344 const iframe = document.querySelector('.contestio-iframe'); 345 346 if (!container || !iframe) { 347 logger.warn(`container or iframe not found [${Date.now() - startTime}ms]`); 348 return; 349 } 350 351 logger.log(`Found iframe and container, initializing features [${Date.now() - startTime}ms]`); 352 353 if (iframe.dataset.contestioIframeLoaded === 'true') { 354 iframeLoaded = true; 355 } 356 357 if (!iframe.dataset.contestioLoadListenerAttached) { 358 iframe.addEventListener('load', () => { 359 iframeLoaded = true; 360 iframe.dataset.contestioIframeLoaded = 'true'; 361 logger.log('contestio.js - iframe load detected, flushing queue'); 362 flushPendingNavigationActions(); 363 }); 364 iframe.dataset.contestioLoadListenerAttached = 'true'; 365 } 366 367 if (!iframeLoaded) { 368 try { 369 const href = iframe.contentWindow && iframe.contentWindow.location && iframe.contentWindow.location.href; 370 if (href && href !== 'about:blank') { 371 // Same-origin blank document, waiting for remote load. 372 } 373 } catch (error) { 374 iframeLoaded = true; 375 iframe.dataset.contestioIframeLoaded = 'true'; 376 logger.log('contestio.js - iframe already loaded, flushing queue'); 377 flushPendingNavigationActions(); 378 } 379 } 380 381 sendNavigationUpdateToIframe('init', { 382 trackAck: true, 383 notifyAfterAck: false, 384 }); 385 386 // Initialize keyboard manager 387 new KeyboardManager(iframe); 388 389 function adjustHeight() { 390 const mainContentElt = document.querySelector('#maincontent'); 391 const containerElt = document.querySelector('.contestio-container'); 392 393 if (!mainContentElt || !containerElt) { 394 logger.warn('mainContentElt or containerElt not found'); 395 return; 396 } 397 398 let offset = mainContentElt.offsetTop || 0; 399 const windowHeight = window.innerHeight; 400 const newHeight = windowHeight - offset; 401 402 logger.log(`Adjusting height to: ${newHeight}px [${Date.now() - startTime}ms]`); 403 containerElt.style.height = `${newHeight}px`; 404 } 405 406 // Attendre un peu avant ajustement hauteur 407 setTimeout(() => { 408 logger.log(`Initial height adjustment [${Date.now() - startTime}ms]`); 409 adjustHeight(); 410 }, 300); 411 412 window.addEventListener('resize', adjustHeight); 413 414 // Message listener setup 415 function createMessageListener() { 416 const messageHandler = async (event) => { 417 const iframeElt = document.querySelector('.contestio-iframe'); 418 if (!iframeElt) { 419 logger.warn('Iframe element not found'); 420 return; 421 } 422 423 const iframeOrigin = normalizeOrigin(iframeElt.src); 424 const baseOrigin = normalizeOrigin(iframeElt.dataset?.contestioBaseUrl); 425 if (baseOrigin) { 426 allowedIframeOrigins.add(baseOrigin); 427 } 428 if (iframeOrigin) { 429 allowedIframeOrigins.add(iframeOrigin); 430 } 431 432 if (!event.origin) { 433 return; 434 } 435 436 if (!allowedIframeOrigins.has(event.origin)) { 437 logger.log('Message received from unauthorized origin:', event.origin, { 438 allowed: Array.from(allowedIframeOrigins.values()), 439 }); 440 return; 441 } 442 443 if (!event.data || typeof event.data !== 'object' || !event.data.type) { 444 return; 445 } 446 447 const replyOrigin = 448 event.origin || 449 normalizeOrigin(iframeElt.src) || 450 normalizeOrigin(iframeElt.dataset?.contestioBaseUrl); 451 if (!replyOrigin) { 452 logger.warn('contestio.js - unable to determine reply origin', event); 453 return; 454 } 455 456 const { type, loginCredentials, pathname, redirectUrl, clipboardText, cookie } = event.data; 457 458 logger.log(`Processing message type: ${type}`); 459 460 try { 461 switch (type) { 462 case 'login': 463 const url = window.location.href.includes('?') 464 ? window.location.href.split('?')[0] 465 : window.location.href; 466 467 const loginUrl = url.endsWith('/') ? url + 'login' : url + '/login'; 468 logger.log('Attempting login to:', loginUrl); 469 470 const response = await fetch(loginUrl, { 471 method: 'POST', 472 headers: { 'Content-Type': 'application/json' }, 473 body: JSON.stringify({ 474 username: loginCredentials.username, 475 password: loginCredentials.password 476 }), 477 }); 478 479 const data = await response.json(); 480 481 if (data.success) { 482 logger.log('Login successful, reloading page'); 483 window.location.reload(); 484 } else { 485 event.source.postMessage({ 486 loginResponse: { 487 success: false, 488 message: data.message, 489 data: data 490 } 491 }, replyOrigin); 492 } 493 break; 494 495 case 'pathname': 496 const currentUrl = new URL(window.location.href); 497 currentUrl.searchParams.delete('l'); 498 currentUrl.searchParams.delete('u'); 499 500 if (pathname !== '' && pathname !== '/') { 501 currentUrl.searchParams.set('l', pathname); 502 } else { 503 currentUrl.searchParams.delete('l'); 504 } 505 506 history.pushState(null, null, currentUrl.toString()); 507 sendNavigationUpdateToIframe('push'); 508 break; 509 510 case 'history-push': 511 const pushUrl = new URL(window.location.href); 512 pushUrl.searchParams.delete('l'); 513 pushUrl.searchParams.delete('u'); 514 515 if (pathname !== '' && pathname !== '/') { 516 pushUrl.searchParams.set('l', pathname); 517 } else { 518 pushUrl.searchParams.delete('l'); 519 } 520 521 const newPushUrl = pushUrl.toString(); 522 523 if (window.location.href !== newPushUrl) { 524 history.pushState({ title: event.data.title }, event.data.title || '', newPushUrl); 525 if (event.data.title) { 526 document.title = event.data.title; 527 } 528 sendNavigationUpdateToIframe('push'); 529 } 530 break; 531 532 case 'history-replace': 533 const replaceUrl = new URL(window.location.href); 534 replaceUrl.searchParams.delete('l'); 535 replaceUrl.searchParams.delete('u'); 536 537 if (pathname !== '' && pathname !== '/') { 538 replaceUrl.searchParams.set('l', pathname); 539 } else { 540 replaceUrl.searchParams.delete('l'); 541 } 542 543 const newReplaceUrl = replaceUrl.toString(); 544 if (window.location.href !== newReplaceUrl) { 545 history.replaceState({ title: event.data.title }, event.data.title || '', newReplaceUrl); 546 if (event.data.title) { 547 document.title = event.data.title; 548 } 549 sendNavigationUpdateToIframe('replace'); 550 } 551 break; 552 553 case 'history-back': 554 logger.log('History back'); 555 if (window.history.length > 1) { 556 history.back(); 557 } 558 break; 559 560 case 'request-parent-path': 561 logger.log('Iframe requested parent path sync'); 562 sendNavigationUpdateToIframe('sync', { 563 trackAck: true, 564 notifyAfterAck: false, 565 }); 566 break; 567 568 case 'ack-path': 569 { 570 const ackPath = pathname || event.data.fullPath || '/'; 571 // ack-path message received (verbose log removed) 572 handleAckFromIframe(ackPath); 573 } 574 break; 575 576 case 'redirect': 577 logger.log('Redirect to:', redirectUrl); 578 window.location.href = redirectUrl; 579 break; 580 581 case 'clipboard': 582 logger.log('Copy to clipboard:', clipboardText); 583 await navigator.clipboard.writeText(clipboardText); 584 break; 585 586 case 'createCookie': 587 const isHttps = window.location.protocol === 'https:'; 588 let cookieStr; 589 if (isHttps) { 590 cookieStr = `${cookie.name}=${cookie.value}; expires=${cookie.expires};
590 path=${cookie.path || '/'}; SameSite=None; Secure`; 591 } else { 592 cookieStr = `${cookie.name}=${cookie.value}; expires=${cookie.expires}; path=${cookie.path || '/'}; SameSite=Lax`; 593 } 594 document.cookie = cookieStr; 595 logger.log('Create cookie:', cookie, cookieStr); 596 break; 597 598 case 'getCookie': 599 const cookieValue = document.cookie.split('; ').find(row => row.startsWith(`${cookie.name}=`))?.split('=')[1]; 600 logger.log('Get cookie:', cookie, cookieValue); 601 event.source.postMessage({ 602 getCookieResponse: { 603 success: cookieValue ? true : false, 604 cookieName: cookie.name, 605 cookieValue: cookieValue 606 } 607 }, replyOrigin); 608 break; 609 610 case 'deleteCookie': 611 const isHttpsDelete = window.location.protocol === 'https:'; 612 let deleteCookieStr; 613 if (isHttpsDelete) { 614 deleteCookieStr = `${cookie.name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${cookie.path || '/'}; SameSite=None; Secure`; 615 } else { 616 deleteCookieStr = `${cookie.name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${cookie.path || '/'}; SameSite=Lax`; 617 } 618 document.cookie = deleteCookieStr; 619 logger.log('Delete cookie:', cookie, deleteCookieStr); 620 break; 621 622 default: 623 logger.warn('Unknown message type:', type); 624 } 625 } catch (error) { 626 logger.error('Error processing message:', error); 627 628 if (type === 'login') { 629 event.source.postMessage({ 630 loginResponse: { 631 success: false, 632 message: "Erreur lors du traitement de la requête", 633 error: error.message 634 } 635 }, replyOrigin); 636 } 637 } 638 }; 639 640 window.addEventListener('message', messageHandler); 641 642 return () => { 643 logger.log('Cleaning up message listener'); 644 window.removeEventListener('message', messageHandler); 645 window.__contestioMessageListenerActive = false; 646 }; 647 } 648 649 let cleanup = window.__contestioMessageListenerCleanup || null; 650 window.__contestioMessageListenerActive = window.__contestioMessageListenerActive || false; 651 652 function setupListener() { 653 if (window.__contestioMessageListenerActive) { 654 logger.log('contestio.js - message listener already active, skipping setup'); 655 return; 656 } 657 logger.log(`Setting up message listener [${Date.now() - startTime}ms]`); 658 if (cleanup) { 659 cleanup(); 660 } 661 cleanup = createMessageListener(); 662 window.__contestioMessageListenerCleanup = cleanup; 663 window.__contestioMessageListenerActive = true; 664 } 665 666 setupListener(); 667 668 document.addEventListener('visibilitychange', () => { 669 if (document.visibilityState === 'visible') { 670 logger.log('Visibility changed to visible, reconfiguring listener'); 671 setupListener(); 672 } 673 }); 674 }; 675 676 // Démarrer l'attente 677 waitForIframe(); 678 } 679 680 logger.log(`Document readyState: ${document.readyState}`); 681 682 // Initialisation 683 if (document.readyState === 'loading') { 684 logger.log('Waiting for DOMContentLoaded'); 685 document.addEventListener('DOMContentLoaded', init); 686 } else { 687 logger.log('DOM ready, executing immediately'); 688 init(); 689 } 690 691 window.addEventListener('popstate', handleParentPopstate); 692})();
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.