1/*! 2 * iframemanager v1.2.5 3 * Author Orest Bida 4 * Released under the MIT License 5 */ 6(() => { 7 'use strict'; 8 9 /** 10 * @typedef {Object} servicePropObj 11 * @property {string} _id 12 * @property {string} _title 13 * @property {string} _thumbnail 14 * @property {string} _params 15 * @property {HTMLDivElement} _div 16 * @property {HTMLDivElement} _innerContainer 17 * @property {HTMLDivElement} _placeholderDiv 18 * @property {HTMLDivElement} _initialPlaceholderClone 19 * @property {HTMLIFrameElement} _iframe 20 * @property {HTMLDivElement} _backgroundDiv 21 * @property {boolean} _hasIframe 22 * @property {boolean} _hasNotice 23 * @property {boolean} _showNotice 24 * @property {boolean} _dataWidget 25 * @property {boolean} _dataPlaceholderVisible 26 * @property {Object.<string, string>} _iframeAttributes 27 */ 28 29 /** 30 * @typedef {HTMLIFrameElement} IframeProp 31 */ 32 33 /** 34 * @typedef {Object} CookieStructure 35 * @property {string} name 36 * @property {string} path 37 * @property {string} domain 38 * @property {string} sameSite 39 * @property {number} expiration 40 */ 41 42 /** 43 * @typedef {Object} Language 44 * @property {string} notice 45 * @property {string} loadBtn 46 * @property {string} loadAllBtn 47 */ 48 49 /** 50 * @typedef {Object} ServiceConfig 51 * @property {string} embedUrl 52 * @property {string | () => string} [thumbnailUrl] 53 * @property {IframeProp} [iframe] 54 * @property {CookieStructure} cookie 55 * @property {Object.<string, Language>} languages 56 * @property {Function} [onAccept] 57 * @property {Function} [onReject] 58 */ 59 60 const API_EVENT_SOURCE = 'api'; 61 const CLICK_EVENT_SOURCE = 'click'; 62 const DATA_ID_PLACEHOLDER = '{data-id}'; 63 const ACCEPT_ACTION = 'accept'; 64 const REJECT_ACTION = 'reject'; 65 66 const HIDE_NOTICE_CLASS = 'c-h-n'; 67 const HIDE_LOADER_CLASS = 'c-h-b'; 68 const SHOW_PLACEHOLDER_CLASS = 'show-ph'; 69 70 let 71 72 /** 73 * @type {Window} 74 */ 75 win, 76 77 /** 78 * @type {Document} 79 */ 80 doc, 81 82 config, 83 84 /** 85 * @type {Object.<string, servicePropObj[]>} 86 */ 87 allServiceProps = {}, 88 89 /** 90 * @type {Object.<string, IntersectionObserver>} 91 */ 92 serviceObservers = {}, 93 94 /** 95 * @type {Object.<string, boolean>} 96 */ 97 stopServiceObserver = {}, 98 99 currLang = '', 100 101 /** 102 * @type {Object.<string, ServiceConfig>} 103 */ 104 services = {}, 105 106 /** 107 * @type {string[]} 108 */ 109 serviceNames, 110 111 /** 112 * @type {Map<string, boolean>} 113 */ 114 servicesState = new Map(), 115 116 /** 117 * @type {'api' | 'click'} 118 */ 119 currentEventSource = API_EVENT_SOURCE, 120 121 onChangeCallback; 122 123 /** 124 * Prevent direct use of the following 125 * props. in the `iframe` element to avoid 126 * potential issues 127 */ 128 const disallowedProps = ['onload', 'onerror', 'src']; 129 130 const isFunction = el => typeof el === 'function'; 131 const isString = el => typeof el === 'string'; 132 const getBrowserLang = () => navigator.language.slice(0, 2).toLowerCase(); 133 134 /** 135 * Create and return HTMLElement based on specified type 136 * @param {string} type 137 */ 138 const createNode = (type) => doc.createElement(type); 139 140 /** 141 * @returns {HTMLDivElement} 142 */ 143 const createDiv = () => createNode('div'); 144 145 /** 146 * @returns {HTMLButtonElement} 147 */ 148 const createButton = () => { 149 const btn = createNode('button'); 150 btn.type = 'button'; 151 return btn; 152 }; 153 154 /** 155 * @param {HTMLElement} el 156 * @param {string} className 157 */ 158 const setClassName = (el, className) => el.className = className; 159 160 /** 161 * @param {HTMLElement} el 162 * @param {string} className 163 * @returns 164 */ 165 const addClass = (el, className) => el.classList.add(className); 166 167 /** 168 * @param {HTMLElement} parent 169 * @param {HTMLElement} child 170 */ 171 const appendChild = (parent, child) => parent.appendChild(child); 172 173 /** 174 * @returns {string[]} 175 */ 176 const getKeys = obj => obj && Object.keys(obj) || []; 177 178 /** 179 * @param {HTMLIFrameElement} iframe 180 * @param {Object.<string, string>} attrs 181 */ 182 const setIframeAttributes = (iframe, attrs) => { 183 for(const key in attrs) 184 setAttribute(iframe, key, attrs[key]); 185 }; 186 187 /** 188 * @param {HTMLDivElement} serviceDiv 189 * @returns {servicePropObj} 190 */ 191 const getDivProps = (serviceDiv) => { 192 193 const dataset = serviceDiv.dataset; 194 const iframeAttrs = {}; 195 const iframeAttrSelector = 'data-iframe-'; 196 197 const iframeAttrNames = serviceDiv.getAttributeNames() 198 .filter(attr => attr.slice(0, 12) === iframeAttrSelector) 199 .map(attr => attr.slice(12)); 200 201 const placeholderDiv = serviceDiv.querySelector('[data-placeholder]'); 202 const dataVisible = placeholderDiv && placeholderDiv.hasAttribute('data-visible'); 203 dataVisible && placeholderDiv.removeAttribute('data-visible'); 204 const placeholderClone = placeholderDiv && placeholderDiv.cloneNode(true); 205 206 /** 207 * Get all "data-iframe-* attributes 208 */ 209 for(const attrName of iframeAttrNames) 210 iframeAttrs[attrName] = serviceDiv.getAttribute(iframeAttrSelector + attrName); 211 212 return { 213 _id: dataset.id, 214 _title: dataset.title, 215 _thumbnail: dataset.thumbnail, 216 _params: dataset.params, 217 _div: serviceDiv, 218 _innerContainer: null, 219 _placeholderDiv: placeholderDiv, 220 _initialPlaceholderClone: placeholderClone, 221 _backgroundDiv: null, 222 _hasIframe: false, 223 _hasNotice: false, 224 _showNotice: true, 225 _dataWidget: 'widget' in dataset, 226 _dataPlaceholderVisible: dataVisible, 227 _iframeAttributes: iframeAttrs 228 }; 229 }; 230 231 /** 232 * @param {string} serviceName 233 * @param {string} thumbnailUrl 234 */ 235 const lazyLoadThumbnails = (serviceName, thumbnailUrl) => { 236 237 const serviceProps = allServiceProps[serviceName]; 238 239 if ('IntersectionObserver' in win) { 240 const thumbnailObserver = new IntersectionObserver((entries) => {
241 for(const entry of entries){ 242 if(entry.isIntersecting){ 243 // index relative to the current service array 244 loadThumbnail(thumbnailUrl, serviceProps[entry.target.dataset.index]); 245 thumbnailObserver.unobserve(entry.target); 246 } 247 } 248 }); 249 250 for(const serviceProp of serviceProps) 251 thumbnailObserver.observe(serviceProp._div); 252 } 253 }; 254 255 256 /** 257 * @param {string} url 258 * @param {servicePropObj} serviceProp 259 */ 260 const loadThumbnail = (url, serviceProp) => { 261 262 const loadBackgroundImage = (src) => { 263 serviceProp._backgroundDiv.style.backgroundImage = `url('${src}')`; 264 265 const img = new Image(); 266 img.onload = () => addClass(serviceProp._backgroundDiv, 'loaded'); 267 img.src = src; 268 }; 269 270 // Set custom thumbnail if provided 271 if(isString(serviceProp._thumbnail)){ 272 serviceProp._thumbnail !== '' && loadBackgroundImage(serviceProp._thumbnail); 273 }else{ 274 275 if(isFunction(url)){ 276 url(serviceProp._id, (src) => loadBackgroundImage(src)); 277 278 }else if(isString(url)){ 279 const src = url.replace(DATA_ID_PLACEHOLDER, serviceProp._id); 280 loadBackgroundImage(src); 281 } 282 } 283 284 }; 285 286 /** 287 * Create iframe and append it into the specified div 288 * @param {servicePropObj} serviceProp 289 * @param {ServiceConfig} serviceConfig 290 */ 291 const createIframe = (serviceProp, serviceConfig) => { 292 293 // Create iframe only if doesn't already have one 294 if(serviceProp._hasIframe) 295 return; 296 297 serviceProp._hasIframe = true; 298 299 if(serviceProp._placeholderDiv){ 300 const newFreshPlaceholder = serviceProp._initialPlaceholderClone.cloneNode(true); 301 serviceProp._placeholderDiv.replaceWith(newFreshPlaceholder); 302 serviceProp._placeholderDiv = newFreshPlaceholder; 303 } 304 305 const iframeProps = serviceConfig.iframe; 306 307 if(isFunction(serviceConfig.onAccept)){ 308 309 // Let the onAccept method create the iframe 310 serviceConfig.onAccept(serviceProp._div, (iframe) => { 311 312 if(!(iframe instanceof HTMLIFrameElement)) 313 return false; 314 315 /** 316 * Add global internal attributes 317 */ 318 setIframeAttributes(iframe, iframeProps); 319 320 /** 321 * Add all data-attr-* attributes (iframe specific) 322 */ 323 setIframeAttributes(iframe, serviceProp._iframeAttributes); 324 325 serviceProp._iframe = iframe; 326 serviceProp._hasIframe = true; 327 328 // Hide loading circle 329 addClass(serviceProp._div, HIDE_LOADER_CLASS); 330 331 // Show placeholder 332 (!serviceProp._dataPlaceholderVisible || serviceProp._dataWidget) 333 && addClass(serviceProp._div, SHOW_PLACEHOLDER_CLASS); 334 }); 335 336 return; 337 } 338 339 serviceProp._iframe = createNode('iframe'); 340 341 const iframeConfig = serviceConfig.iframe; 342 343 /** 344 * @type {string} 345 */ 346 const iframeParams = serviceProp._params || iframeConfig && iframeConfig.params; 347 348 // Replace data-id with valid resource id 349 const embedUrl = serviceConfig.embedUrl || ''; 350 let src = embedUrl.replace(DATA_ID_PLACEHOLDER, serviceProp._id); 351 352 serviceProp._title && (serviceProp._iframe.title = serviceProp._title); 353 354 // Add parameters to src 355 if(iframeParams && isString(iframeParams)){ 356 src += iframeParams.slice(0, 1) === '?' 357 ? iframeParams 358 : `?${iframeParams}`; 359 } 360 361 // When iframe is loaded => hide background image 362 serviceProp._iframe.onload = () => { 363 addClass(serviceProp._div, HIDE_LOADER_CLASS); 364 serviceProp._iframe.onload = undefined; 365 366 isFunction(iframeProps && iframeProps.onload) 367 && iframeProps.onload(serviceProp._id, serviceProp._iframe); 368 }; 369 370 /** 371 * Add global internal attributes 372 */ 373 setIframeAttributes(serviceProp._iframe, iframeProps); 374 375 /** 376 * Add all data-attr-* attributes (iframe specific) 377 */ 378 setIframeAttributes(serviceProp._iframe, serviceProp._iframeAttributes); 379 380 serviceProp._iframe.src = src; 381 382 appendChild(serviceProp._innerContainer, serviceProp._iframe); 383 }; 384 385 /** 386 * @param {HTMLElement} el 387 * @param {string} attrKey 388 * @param {string} attrValue 389 */ 390 const setAttribute = (el, attrKey, attrValue) => { 391 if(!disallowedProps.includes(attrKey)) 392 el.setAttribute(attrKey, attrValue); 393 }; 394 395 /** 396 * Remove iframe HTMLElement from div 397 * @param {servicePropObj} serviceProp 398 */ 399 const removeIframe = (serviceProp) => { 400 serviceProp._iframe && serviceProp._iframe.remove(); 401 serviceProp._hasIframe = false; 402 }; 403 404 /** 405 * Add necessary classes to hide notice 406 * @param {servicePropObj} serviceProp 407 */ 408 const hideNotice = (serviceProp) => { 409 addClass(serviceProp._div, HIDE_NOTICE_CLASS); 410 serviceProp._showNotice = false; 411 }; 412 413 /** 414 * Add necessary classes to show notice 415 * @param {servicePropObj} serviceProp 416 */ 417 const showNotice = (serviceProp) => { 418 serviceProp._div.classList.remove( 419 HIDE_NOTICE_CLASS, 420 HIDE_LOADER_CLASS, 421 SHOW_PLACEHOLDER_CLASS 422 ); 423 serviceProp._showNotice = true; 424 }; 425 426 /** 427 * Get cookie by name 428 * @param {string} a cookie name 429 * @returns {string} cookie value 430 */ 431 const getCookie = (a) => { 432 a = doc.cookie.match(`(^|;)\\s*${a}\\s*=\\s*([^;]+)`); 433 434 return a ? a.pop() : ''; 435 }; 436 437 /** 438 * Set cookie based on given object 439 * @param {CookieStructure} cookie 440 */
441 const setCookie = (cookie) => { 442 443 const { hostname, protocol } = location; 444 const name = cookie.name; 445 const value = '1'; 446 const date = new Date(); 447 const path = cookie.path || '/'; 448 const expiration = (cookie.expiration || 182) * 86400000; 449 const sameSite = cookie.sameSite || 'Lax'; 450 const domain = cookie.domain || hostname; 451 452 date.setTime(date.getTime() + expiration); 453 454 let cookieStr = name + '=' 455 + value 456 + (expiration !== 0 ? `; Expires=${date.toUTCString()}` : '') 457 + `; Path=${path}` 458 + `; SameSite=${sameSite}`; 459 460 // assures cookie works with localhost (=> don't specify domain if on localhost) 461 if(domain.indexOf('.') > -1) 462 cookieStr += `; Domain=${domain}`; 463 464 if(protocol === 'https:') 465 cookieStr += '; Secure'; 466 467 doc.cookie = cookieStr; 468 }; 469 470 /** 471 * Delete cookie by name & path 472 * @param {CookieStructure} cookie 473 */ 474 const eraseCookie = (cookie) => { 475 const name = cookie.name; 476 const path = cookie.path || '/'; 477 const domain = cookie.domain || location.hostname; 478 const expires = 'Thu, 01 Jan 1970 00:00:01 GMT'; 479 480 doc.cookie = `${name}=; Path=${path}; Domain=${domain}; Expires=${expires};`; 481 }; 482 483 /** 484 * Create all notices relative to the specified service 485 * @param {string} serviceName 486 * @param {ServiceConfig} serviceConfig 487 * @param {boolean} hidden 488 */ 489 const createAllNotices = (serviceName, serviceConfig, hidden) => { 490 491 // get number of iframes of current service 492 const serviceProps = allServiceProps[serviceName]; 493 const languages = serviceConfig.languages; 494 495 serviceProps.forEach(serviceProp => { 496 497 if(!serviceProp._hasNotice && languages){ 498 const lang = languages[currLang]; 499 const loadBtnText = lang && lang.loadBtn; 500 const noticeText = lang && lang.notice; 501 const loadAllBtnText = lang && lang.loadAllBtn; 502 503 const fragment = doc.createElement('div'); 504 const notice = createDiv(); 505 const span = createDiv(); 506 const innerDiv = createDiv(); 507 const buttons = createDiv(); 508 509 setClassName(fragment, 'cll'); 510 serviceProp._innerContainer = fragment; 511 512 const showVideo = () => { 513 hideNotice(serviceProp); 514 createIframe(serviceProp, serviceConfig); 515 }; 516 517 if(loadBtnText){ 518 const load_button = createButton(); 519 load_button.textContent = loadBtnText; 520 setClassName(load_button, 'c-l-b'); 521 522 load_button.addEventListener(CLICK_EVENT_SOURCE, showVideo); 523 appendChild(buttons, load_button); 524 } 525 526 if(loadAllBtnText){ 527 const load_all_button = createButton(); 528 load_all_button.textContent = loadAllBtnText; 529 setClassName(load_all_button, loadBtnText ? 'c-la-b' : 'c-l-b'); 530 531 load_all_button.addEventListener(CLICK_EVENT_SOURCE, () => { 532 showVideo(); 533 534 currentEventSource = CLICK_EVENT_SOURCE; 535 api.acceptService(serviceName); 536 }); 537 538 appendChild(buttons, load_all_button); 539 } 540 541 const notice_text = createDiv(); 542 const ytVideoBackground = createDiv(); 543 const loaderBg = createDiv(); 544 const ytVideoBackgroundInner = createDiv(); 545 const notice_text_container = createDiv(); 546 547 setClassName(notice_text, 'cc-text'); 548 setClassName(ytVideoBackgroundInner, 'c-bg-i'); 549
550 serviceProp._backgroundDiv = ytVideoBackgroundInner; 551 setClassName(loaderBg, 'c-ld'); 552 553 if(!isString(serviceProp._thumbnail) || serviceProp._thumbnail !== ''){ 554 setClassName(ytVideoBackground, 'c-bg'); 555 } 556 557 const iframeTitle = serviceProp._title; 558 const fragment_2 = doc.createDocumentFragment(); 559 560 if(iframeTitle) { 561 const title_span = createNode('span'); 562 setClassName(title_span, 'c-tl'); 563 title_span.insertAdjacentHTML('beforeend', iframeTitle); 564 appendChild(fragment_2, title_span); 565 } 566 567 appendChild(notice_text, fragment_2); 568 notice && notice_text.insertAdjacentHTML('beforeend', noticeText || ''); 569 appendChild(span, notice_text); 570 571 setClassName(notice_text_container, 'c-t-cn'); 572 setClassName(span, 'c-n-t'); 573 setClassName(innerDiv, 'c-n-c'); 574 setClassName(notice, 'c-nt'); 575 setClassName(buttons, 'c-n-a'); 576 577 appendChild(notice_text_container, span); 578 579 if(loadBtnText || loadAllBtnText) 580 appendChild(notice_text_container, buttons); 581 582 appendChild(innerDiv, notice_text_container); 583 appendChild(notice, innerDiv); 584 585 appendChild(ytVideoBackground, ytVideoBackgroundInner); 586 appendChild(fragment, notice); 587 (serviceConfig.thumbnailUrl || serviceProp._thumbnail) && appendChild(fragment, ytVideoBackground); 588 appendChild(fragment, loaderBg); 589 590 hidden && addClass(serviceProp._div, HIDE_NOTICE_CLASS); 591 592 // Avoid reflow with fragment (only 1 appendChild) 593 serviceProp._div.prepend(fragment); 594 serviceProp._hasNotice = true; 595 596 setTimeout(()=> addClass(serviceProp._div, 'c-an'), 20); 597 } 598 }); 599 }; 600 601 /** 602 * Hide notices for the specified service 603 * and then create iframes. 604 * 605 * @param {string} serviceName 606 * @param {ServiceConfig} serviceConfig 607 */ 608 const hideAllNotices = (serviceName, serviceConfig) => { 609 610 // get number of iframes of current service 611 const serviceProps = allServiceProps[serviceName]; 612 613 const observeServiceDiv = (div, serviceName) => { 614 615 if(!serviceObservers[serviceName]) { 616 serviceObservers[serviceName] = new IntersectionObserver((entries) => { 617 618 if(stopServiceObserver[serviceName]){ 619 serviceObservers[serviceName].disconnect(); 620 return; 621 } 622 623 for(let i=0; i<entries.length; ++i){ 624 if(entries[i].isIntersecting){ 625 ((i) => { 626 /** 627 * @type {HTMLDivElement} 628 */ 629 const target = entries[i].target; 630 const dataIndex = target.dataset.index; 631 createIframe(serviceProps[dataIndex], serviceConfig); 632 633 setTimeout(() => { 634 hideNotice(serviceProps[dataIndex]); 635 }, i*50); 636 637 serviceObservers[serviceName].unobserve(target); 638 })(i); 639 } 640 } 641 642 }); 643 } 644 645 serviceObservers[serviceName].observe(div); 646 }; 647 648 serviceProps.forEach((serviceProp) => { 649 if(!serviceProp._hasIframe) 650 observeServiceDiv(serviceProp._div, serviceName); 651 }); 652 }; 653 654 655 /** 656 * Show notices for the specified service 657 * and remove iframes. 658 * 659 * @param {string} serviceName 660 * @param {ServiceConfig}
660 serviceConfig 661 */ 662 const showAllNotices = (serviceName, serviceConfig) => { 663 664 const serviceProps = allServiceProps[serviceName]; 665 666 for(let i=0; i<serviceProps.length; i++){ 667 ((i) => { 668 669 /** 670 * Create iframe if it doesn't exist 671 */ 672 if(serviceProps[i]._hasIframe){ 673 if(isFunction(serviceConfig.onReject)){ 674 serviceConfig.onReject(serviceProps[i]._iframe, serviceProps[i]._div, () => showNotice(serviceProps[i])); 675 serviceProps[i]._hasIframe = false; 676 } else { 677 removeIframe(serviceProps[i]); 678 } 679 } 680 681 showNotice(serviceProps[i]); 682 })(i); 683 } 684 }; 685 686 /** 687 * Validate language (make sure it exists) 688 * 689 * @param {string} lang 690 * @param {Object.<string, Object>} allLanguages 691 * @returns {string} language 692 */ 693 const getValidatedLanguage = (lang, allLanguages) => { 694 if(lang in allLanguages){ 695 return lang; 696 }else if(getKeys(allLanguages).length > 0){ 697 return currLang in allLanguages 698 ? currLang 699 : getKeys(allLanguages)[0]; 700 } 701 }; 702 703 /** 704 * @param {string} serviceName 705 * @param {ServiceConfig} serviceConfig 706 */ 707 const acceptHelper = (serviceName, serviceConfig) => { 708 const { cookie } = serviceConfig; 709 710 if(!getCookie(cookie.name)) 711 setCookie(cookie); 712 713 hideAllNotices(serviceName, serviceConfig); 714 }; 715 716 /** 717 * @param {string} serviceName 718 * @param {ServiceConfig} serviceConfig 719 */ 720 const rejectHelper = (serviceName, serviceConfig) => { 721 const { cookie } = serviceConfig; 722 723 if(getCookie(cookie.name)) 724 eraseCookie(cookie); 725 726 showAllNotices(serviceName, serviceConfig); 727 }; 728 729 /** 730 * @param {string} serviceName 731 * @param {'accept' | 'reject'} action 732 * @param {string[]} changedServices 733 */ 734 const fireOnChangeCallback = (serviceName, action, changedServices) => { 735 isFunction(onChangeCallback) && onChangeCallback({ 736 eventSource: { 737 type: currentEventSource, 738 service: serviceName, 739 action 740 }, 741 changedServices 742 }); 743 }; 744 745 const api = { 746 747 /** 748 * @param {string} serviceName 749 */ 750 acceptService: (serviceName) => { 751 const changedServices = []; 752 753 if(serviceName === 'all'){ 754 755 for(const name of serviceNames){ 756 stopServiceObserver[name] = false; 757 758 if(!servicesState.get(name)){ 759 servicesState.set(name, true); 760 acceptHelper(name, services[name]); 761 changedServices.push(name); 762 } 763 } 764 765 changedServices.length > 0 && fireOnChangeCallback(serviceName, ACCEPT_ACTION, changedServices); 766 767 }else if(serviceNames.includes(serviceName)){ 768 stopServiceObserver[serviceName] = false; 769 770 if(!servicesState.get(serviceName)){ 771 servicesState.set(serviceName, true); 772 acceptHelper(serviceName, services[serviceName]); 773 changedServices.push(serviceName); 774 fireOnChangeCallback(serviceName, ACCEPT_ACTION, changedServices); 775 } 776 } 777 778 currentEventSource = API_EVENT_SOURCE; 779 }, 780 781 /** 782 * @param {string} serviceName 783 */ 784 rejectService: (serviceName) => { 785 786 const changedServices = []; 787 788 if(serviceName === 'all'){ 789 790 for(const name of serviceNames){ 791 stopServiceObserver[name] = true; 792 rejectHelper(name, services[name]); 793 794 if(servicesState.get(name)){ 795 servicesState.set(name, false); 796 changedServices.push(name); 797 } 798 } 799 800 changedServices.length >
800 0 && fireOnChangeCallback(serviceName, REJECT_ACTION, changedServices); 801 802 }else if(serviceNames.includes(serviceName)){ 803 stopServiceObserver[serviceName] = true; 804 rejectHelper(serviceName, services[serviceName]); 805 806 if(servicesState.get(serviceName)){ 807 servicesState.set(serviceName, false); 808 changedServices.push(serviceName); 809 810 fireOnChangeCallback(serviceName, REJECT_ACTION, changedServices); 811 } 812 } 813 }, 814 815 /** 816 * Check if a property/element is defined, 817 * if it's not then check again; repeat until maxTimeout reached. 818 * 819 * Useful when trying to use API from external scripts, 820 * or when you need to make sure a dom element exists 821 * (e.g. dynamically generated iframe). 822 * 823 * @param {object} config 824 * @param {any} [config.parent] 825 * @param {string} [config.childProperty] 826 * @param {string} [config.childSelector] 827 * @param {number} [config.timeout] 828 * @param {number} [config.maxTimeout] 829 * @returns {Promise<boolean>} 830 */ 831 childExists: async ({parent=win, childProperty, childSelector='iframe', timeout=1000, maxTimeout=15000}) => { 832 833 let nTimeouts = 1; 834 835 const child = childProperty 836 ? () => parent[childProperty] 837 : () => parent.querySelector(childSelector); 838 839 return new Promise(resolve => { 840 const checkChild = () => { 841 if (child() || nTimeouts++ * timeout > maxTimeout) 842 return resolve(child() !== undefined); 843 else 844 setTimeout(checkChild, timeout); 845 }; 846 847 checkChild(); 848 }); 849 }, 850 851 getState: () => ({ 852 services: new Map(servicesState), 853 acceptedServices: [...servicesState] 854 .filter(([, value]) => !!value) 855 .map(([name]) => name) 856 }), 857 858 getConfig: () => config, 859 860 run: (_config) => { 861 862 doc = document; 863 win = window; 864 config = _config; 865 866 /** 867 * Object with all services config. 868 */ 869 services = config.services; 870 871 onChangeCallback = config.onChange; 872 873 /** 874 * Array containing the names of all services 875 */ 876 serviceNames = getKeys(services); 877 878 if(serviceNames.length === 0) 879 return; 880 881 // Set curr lang 882 currLang = config.currLang; 883 const languages = services[serviceNames[0]].languages; 884 885 if(config.autoLang === true){ 886 currLang = getValidatedLanguage(getBrowserLang(), languages); 887 }else if(isString(config.currLang)){ 888 currLang = getValidatedLanguage(config.currLang, languages); 889 } 890 891 // for each service 892 for(const serviceName of serviceNames){ 893 894 const currService = services[serviceName]; 895 896 /** 897 * Use service's name as cookie name, 898 * if no cookie.name is specified 899 * @type {CookieStructure} 900 */ 901 const cookieObj = (currService.cookie = currService.cookie || {}); 902 const cookieName = (cookieObj.name = cookieObj.name || `im_${serviceName}`); 903 904 const cookieExists = getCookie(cookieName); 905 servicesState.set(serviceName, !!cookieExists); 906 907 // add new empty array of serviceProps (with current service name as property) 908 allServiceProps[serviceName] = []; 909 910 /** 911 * @type {NodeListOf<HTMLDivElement>} 912 */ 913 const foundDivs = doc.querySelectorAll(`div[data-service="${serviceName}"]`); 914 915 const nDivs = foundDivs.length; 916 917 // if no iframes found =>
917 go to next service 918 if(nDivs === 0){ 919 continue; 920 } 921 922 // add each iframe to array of iframes of the current service 923 for(let j=0; j<nDivs; j++){ 924 foundDivs[j].dataset.index = j; 925 allServiceProps[serviceName].push(getDivProps(foundDivs[j])); 926 } 927 928 // if cookie is not set => show notice 929 if(cookieExists){ 930 createAllNotices(serviceName, currService, true); 931 hideAllNotices(serviceName, currService); 932 }else{ 933 createAllNotices(serviceName, currService, false); 934 } 935 936 lazyLoadThumbnails(serviceName, currService.thumbnailUrl); 937 } 938 } 939 }; 940 941 const fnName = 'iframemanager'; 942 943 if(typeof window !== 'undefined' && !isFunction(window[fnName])){ 944 window[fnName] = () => api; 945 } 946 947})();
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.