IframeManager

repository·main·Indexed 43 days ago

https://github.com/orestbida/iframemanager

A lightweight vanilla JavaScript plugin (v1.3.0) designed for GDPR compliance. It prevents iframes from loading until user consent is given by replacing them with placeholders and notices, supporting custom service configurations, multi-language notices, and custom widgets for third-party services.

Tokens
5.4K
Snippets
13
Records
14
Agent score
5%

What's inside IframeManager

  1. Integrate iframe-manager with CookieConsent

    main

    You can synchronize the state of iframe-manager with CookieConsent (v1.2.0+) by using the onChange callback. When a user clicks the loadAllBtn button, the eventSource.type will be 'click'. You can then retrieve the currently accepted services from CookieConsent and merge them with the changedServices provided by the onChange callback to ensure all newly loaded services are also accepted in CookieConsent.

    im.run({
        currLang: 'en',
    
        onChange: ({changedServices, eventSource}) => {
    
            if(eventSource.type === 'click') {
                /**
                 * Retrieve array of already accepted services
                 * and add the new service
                 */
                const servicesToAccept = [
                    ...CookieConsent.getUserPreferences().acceptedServices['analytics'], //cookieconsent v3
                    ...changedServices
                ];
    
                CookieConsent.acceptService(servicesToAccept, 'analytics');
            }
        },
    
        services: {
            // ...
        }
    });
  2. Initialize and run IframeManager

    main

    To use IframeManager, import the script and stylesheet, then call iframemanager() to create an instance and .run() to apply your configuration.

    As an external script

    Create an app.js file and import it after the iframemanager.js script in your HTML.

    As an inline script

    Wrap your configuration in a window.addEventListener('load', ...) listener to ensure the DOM is ready.

    (function(){
        const im = iframemanager();
    
        im.run({
            currLang: 'en',
            services : {
                youtube : {
                    embedUrl: 'https://www.youtube-nocookie.com/embed/{data-id}',
                    thumbnailUrl: 'https://i3.ytimg.com/vi/{data-id}/hqdefault.jpg',
                    iframe : {
                        allow : 'accelerometer; encrypted-media; gyroscope; picture-in-picture; fullscreen;'
                    },
                    languages : {
                        en : {
                            notice: 'This content is hosted by a third party...',
                            loadBtn: 'Load video',
                            loadAllBtn: "Don't ask again"
                        }
                    }
                }
            }
        });
    })();
  3. Install IframeManager

    main

    You can install IframeManager via NPM or use it via CDN.

    Using NPM

    npm i @orestbida/iframemanager

    Using CDN

    Include the following files in your project:

    • https://cdn.jsdelivr.net/gh/orestbida/iframemanager@1.3.0/dist/iframemanager.js
    • https://cdn.jsdelivr.net/gh/orestbida/iframemanager@1.3.0/dist/iframemanager.css
  4. Show a placeholder for non-JS browsers

    main

    To provide content to users who have disabled JavaScript, use a div with both data-placeholder and data-visible attributes inside your service container. This div will only be visible if JavaScript is not running.

    <div data-service="youtube" data-id="5b35haQV7tU" data-autoscale>
        <div data-placeholder data-visible>
            <p>I'm visible only if js is disabled</p>
        </div>
    </div>
  5. Create custom widgets for third-party services

    main

    If a service (like Twitter) provides its own markup and script to generate iframes, you can implement a custom widget.

    1. Markup: Place the service's markup inside a div with a data-placeholder attribute. Ensure the parent container has data-service="[service_name]" and a fixed width and height to prevent layout shifts.
    2. Implementation: Define the service in the im.run configuration using onAccept and onReject callbacks.
      • onAccept: Dynamically load the required scripts, initialize the widget, and call setIframe with the resulting iframe element.
      • onReject: Remove the element from the DOM.

    Use im.childExists to safely check for the existence of properties or elements during initialization.

    <div data-service="twitter" data-widget style="width: 300px; height: 501px">
        <div data-placeholder>
            <blockquote class="twitter-tweet">...</blockquote>
        </div>
    </div>
    im.run({
        services: {
            twitter: {
                onAccept: async (div, setIframe) => {
                    // Load external script
                    await CookieConsent.loadScript('https://platform.twitter.com/widgets.js');
    
                    // Initialize widget and set the iframe
                    await im.childExists({childProperty: 'twttr'}) && await twttr.widgets.load(div);
                    await im.childExists({parent: div}) && setIframe(div.querySelector('iframe'));
                },
                onReject: (iframe) => {
                    iframe && iframe.parentElement.remove();
                }
            }
        }
    })
  6. Configure Vimeo service

    main

    Vimeo configuration uses an asynchronous thumbnailUrl function to fetch the thumbnail from the Vimeo API v2.

    im.run({
        currLang: 'en',
        services: {
            vimeo: {
                embedUrl: 'https://player.vimeo.com/video/{data-id}',
                iframe: {
                    allow : 'fullscreen; picture-in-picture, allowfullscreen;',
                },
                thumbnailUrl: async (dataId, setThumbnail) => {
                    const url = `https://vimeo.com/api/v2/video/${dataId}.json`;
                    const response = await (await fetch(url)).json();
                    const thumbnailUrl = response[0]?.thumbnail_large;
                    thumbnailUrl && setThumbnail(thumbnailUrl);
                },
                languages: {
                    en: {
                        notice: '...', 
                        loadBtn: 'Load video',
                        loadAllBtn: "Don't ask again"
                    }
                }
            }
        }
    });
  7. Configure YouTube service

    main

    Configure YouTube by providing the embedUrl (using {data-id} as a placeholder), thumbnailUrl, and iframe permissions. You can also define localized languages for the notice, load button, and 'don't ask again' button.

    im.run({
        currLang: 'en',
        services: {
            youtube: {
                embedUrl: 'https://www.youtube-nocookie.com/embed/{data-id}',
                thumbnailUrl: 'https://i.ytimg.com/vi/{data-id}/hqdefault.jpg',
                iframe: {
                    allow: 'accelerometer; encrypted-media; gyroscope; picture-in-picture; fullscreen;',
                },
                languages: {
                    en: {
                        notice: 'This content is hosted by a third party...',
                        loadBtn: 'Load video',
                        loadAllBtn: "Don't ask again"
                    }
                }
            }
        }
    });
    <div data-service="youtube" data-id="5b35haQV7tU"></div>
  8. Configure Dailymotion service

    main

    Dailymotion configuration supports an asynchronous thumbnailUrl function that can fetch data from the Dailymotion API using the dataId.

    im.run({
        currLang: 'en',
        services: {
            dailymotion: {
                embedUrl: 'https://www.dailymotion.com/embed/video/{data-id}',
                thumbnailUrl: async (dataId, setThumbnail) => {
                    const url = `https://api.dailymotion.com/video/${dataId}?fields=thumbnail_large_url`;
                    const response = await (await fetch(url)).json();
                    const thumbnailUlr = response?.thumbnail_large_url;
                    thumbnailUlr && setThumbnail(thumbnailUlr);
                },
                iframe: {
                    allow: 'accelerometer; encrypted-media; gyroscope; picture-in-picture; fullscreen;',
                },
                languages: {
                    en: {
                        notice: '...', 
                        loadBtn: 'Load video',
                        loadAllBtn: "Don't ask again"
                    }
                }
            }
        }
    });
  9. Configure Twitch service

    main

    Twitch configuration requires the embedUrl to include the parent parameter, which should be set to the current location.hostname.

    im.run({
        currLang: 'en',
        services: {
            twitch: {
                embedUrl: `https://player.twitch.tv/?{data-id}&parent=${location.hostname}`,
                iframe: {
                    allow: 'accelerometer; encrypted-media; gyroscope; picture-in-picture; fullscreen;',
                },
                languages: {
                    en: {
                        notice: '...', 
                        loadBtn: 'Load stream',
                        loadAllBtn: "Don't ask again"
                    }
                }
            }
        }
    });
  10. Configure Google Maps service

    main

    Google Maps can be configured in two ways:

    1. With API Key: Use the v1/place endpoint.
    2. Without API Key: Use the standard embed endpoint with a protobuf pb parameter.

    Both methods support data-autoscale on the container div.

    // With API Key
    im.run({
        currLang: 'en',
        services: {
            googlemaps: {
                embedUrl: 'https://www.google.com/maps/embed/v1/place?key=API_KEY&q={data-id}',
                iframe: { allow: 'picture-in-picture; fullscreen;' },
                languages: {
                    en: { notice: '...', loadBtn: 'Load map', loadAllBtn: "Don't ask again" }
                }
            }
        }
    });
    <!-- Example with API Key -->
    <div data-service="GoogleMaps" data-id="Space+Needle,Seattle+WA" data-autoscale></div>
    
    <!-- Example without API Key -->
    <div data-service="googlemaps" data-id="!1m18!1m12!1m3!1d2659..." data-autoscale></div>
  11. Configure services in the IframeManager object

    main

    The .run() method accepts a configuration object to define how different services behave.

    Top-level options:

    • currLang: [String] Current language of the notice (must be defined in languages).
    • autoLang: [Boolean] If true, uses the client's browser language instead of currLang [Optional].
    • onChange: [Function] Callback fired when state changes. Receives ({changedServices, eventSource}) where changedServices is a string[] and eventSource contains type ('api' | 'click') and service (string) and action ('accept' | 'reject'). [Optional].

    Service object properties:

    Each service key (e.g., youtube) contains:

    • embedUrl: [String] The URL template for the iframe. Use {data-id} as a placeholder.
    • thumbnailUrl: [String | Function] URL for automatic thumbnails. Can be a static string, a dynamic string with {data-id}, or a function (dataId, setThumbnail) => void.
    • iframe: [Object] Global settings for all iframes of this service. Includes:
      • allow: [String] Iframe's allow attribute.
      • params: [String] Iframe URL query parameters.
      • onload: [Function] (dataId, setThumbnail) => void called for each iframe [Optional].
      • Any other property added here will be set directly as an attribute on the <iframe> element.
    • cookie: [Object] Settings for the cookie set when the service is accepted. Includes name, path, samesite, and domain [Optional].
    • languages: [Object] Multilanguage support. Each language key must contain a notice string, loadBtn (load single iframe), and loadAllBtn (load all and set cookie).
    {
        currLang: 'en',
        autoLang: false,
        onChange: ({changedServices, eventSource}) => {
            // handle change
        },
        services : {
            myservice : {
                embedUrl: 'https://<myservice_embed_url>/{data-id}',
                thumbnailUrl: (dataId, setThumbnail) => {
                    // fetch and set
                    setThumbnail('https://path/to/image.png');
                },
                iframe: {
                    allow: 'fullscreen',
                    params: 'mute=1',
                    frameborder: '0'
                },
                cookie: {
                    name: 'cc_myservice',
                    path: '/'
                },
                languages: {
                    en: {
                        notice: 'Notice text',
                        loadBtn: 'Load video',
                        loadAllBtn: 'Don\'t ask again'
                    }
                }
            }
        }
    }
  12. Use the iframe-manager APIs

    main

    The plugin provides several methods to control service consent and manage state:

    • run(config): Initializes the manager with the provided configuration.
    • acceptService(service_name): Accepts a specific service or 'all' to enable all services. This sets the consent cookie and creates the iframes.
    • rejectService(service_name): Rejects a specific service or 'all'. This erases the cookie and removes the iframes.
    • getState(): Returns the current state of services.
      • state.services: Map<string, boolean>
      • state.acceptedServices: string[]
    • getConfig(): Returns the entire configuration object.
    • reset(hard_reset):
      • reset() (soft): Removes internal event listeners.
      • reset(true) (hard): Removes event listeners and resets each div to its original state (useful for React frameworks).
    // Accept/Reject specific or all
    im.acceptService('youtube');
    im.acceptService('all');
    im.rejectService('youtube');
    im.rejectService('all');
    
    // State and Config
    const config = im.getConfig();
    const state = im.getState();
    
    // Resetting
    im.reset(); // soft
    im.reset(true); // hard