orestbida / iframemanager

🍪 GDPR friendly iframe manager written in vanilla js
https://orestbida.com/demo-projects/iframemanager/demo1/
MIT License
227 stars 26 forks source link
gdpr gdpr-consent iframe iframe-manager video vimeo youtube

IframeManager Logo

[Demo](https://orestbida.com/demo-projects/iframemanager/demo1/)   |   [Features](#features)   |   [Installation](#installation)    [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) ![Size](https://img.shields.io/github/size/orestbida/iframemanager/dist/iframemanager.js) [![Stable version](https://img.shields.io/github/v/release/orestbida/iframemanager)](https://github.com/orestbida/iframemanager/releases)
**IframeMananger** is a lightweight javascript plugin which helps you **comply with `GDPR`** by completely removing iframes initially and setting a notice relative to that service. **Iframes are loaded only after consent**. The plugin was mainly developed to aid [**CookieConsent**](https://github.com/orestbida/cookieconsent) with iframe management.


Table of Contents

Features

Installation

  1. Download the latest release or use via CDN/NPM:

    https://cdn.jsdelivr.net/gh/orestbida/iframemanager@1.2.5/dist/iframemanager.js
    https://cdn.jsdelivr.net/gh/orestbida/iframemanager@1.2.5/dist/iframemanager.css

    using npm:

    npm i @orestbida/iframemanager
  2. Import script + stylesheet:

    <html>
      <head>
        ...
        <link rel="stylesheet" href="https://github.com/orestbida/iframemanager/blob/main/iframemanager.css">
      </head>
      <body>
        ...
        <script defer src="https://github.com/orestbida/iframemanager/raw/main/iframemanager.js"></script>
      <body>
    </html>
  3. Configure and run:

    • As external script

      - Create a .js file (e.g. `app.js`) and import it in your html markup: ```html ... ``` - Configure iframemanager inside `app.js`: ```javascript (function(){ const im = iframemanager(); // Example with youtube embed 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. By showing the external content you accept the terms and conditions of youtube.com.', loadBtn: 'Load video', loadAllBtn: "Don't ask again" } } } } }); })(); ```

    • As inline script

      ```html ... ```

  4. Create a div with data-service and data-id attributes:

    <div data-service="youtube" data-id="<video-id>"></div>

Configuration options

All available options for the <div> element:

<div
    data-service="<service-name>"
    data-id="<resource-id>"
    data-params="<iframe-query-parameters>"
    data-thumbnail="<path-to-image>"
    data-autoscale
    data-ratio="<x:y>">
</div>

How to set attributes on the iframe element

You can set any attribute by using the following syntax:

Example:

<div
    data-service="youtube"
    data-id="5b35haQV7tU"
    data-autoscale
    data-iframe-id="myYoutubeEmbed"
    data-iframe-loading="lazy"
    data-iframe-frameborder="0">
</div>


All available options for the config. object:

{
    currLang: 'en',     // current language of the notice (must also be defined in the "languages" object below)
    autoLang: false,    // if enabled => use current client's browser language
                        // instead of currLang [OPTIONAL]

    // callback fired when state changes (a new service is accepted/rejected)
    onChange: ({changedServices, eventSource}) => {
        // changedServices: string[]
        // eventSource.type: 'api' | 'click'
        // eventSource.service: string
        // eventSource.action: 'accept' | 'reject'
    },

    services : {
        myservice : {

            embedUrl: 'https://<myservice_embed_url>',

            // set valid url for automatic thumbnails   [OPTIONAL]
            thumbnailUrl: 'https://<myservice_embed_thumbnail_url>',

            // global iframe settings (apply to all iframes relative to current service) [OPTIONAL]
            iframe: {
                allow: 'fullscreen',           // iframe's allow attribute
                params: 'mute=1&start=21',     // iframe's url query parameters

                // function run for each iframe configured with current service
                onload: (dataId, setThumbnail) => {
                    console.log(`loaded iframe with data-id=${dataId}`);
                }
            },

            // cookie is set if the current service is accepted
            cookie: {
                name: 'cc_youtube',            // cookie name
                path: '/',                     // cookie path          [OPTIONAL]
                samesite: 'lax',               // cookie samesite      [OPTIONAL]
                domain: location.hostname      // cookie domain        [OPTIONAL]
            },

            languages: {
                en: {
                    notice: 'Html <b>notice</b> message',
                    loadBtn: 'Load video',          // Load only current iframe
                    loadAllBtn: "Don't ask again"   // Load all iframes configured with this service + set cookie
                }
            }
        },

        anotherservice: {
            // ...
        }
    }
}

Any other property specified inside the iframe object, will be set directly to the iframe element as attribute.

Example: add frameborder and style attributes:

{
    // ...

    services: {
        myservice: {
            // ...

            iframe: {
                // ...

                frameborder: '0',
                style: 'border: 4px solid red;'
            }
        }
    }
}

Note: thumbnailUrl can be static string, dynamic string or a function:

Custom Widgets

Some services (e.g. twitter) have their own markup and API to generate the iframe.

Note: this is an example with twitter's widget. Each widget/service will have a slightly different implementation.

  1. Place the markup inside a special data-placeholder div. Remove any script tag that comes with the markup. Example:

    <div
        data-service="twitter"
        data-widget
        style="width: 300px; height: 501px"
    >
    
        <div data-placeholder>
            <blockquote class="twitter-tweet"><p lang="en" dir="ltr">Sunsets don&#39;t get much better than this one over <a href="https://twitter.com/GrandTetonNPS?ref_src=twsrc%5Etfw">@GrandTetonNPS</a>. <a href="https://twitter.com/hashtag/nature?src=hash&amp;ref_src=twsrc%5Etfw">#nature</a> <a href="https://twitter.com/hashtag/sunset?src=hash&amp;ref_src=twsrc%5Etfw">#sunset</a> <a href="http://t.co/YuKy2rcjyU">pic.twitter.com/YuKy2rcjyU</a></p>&mdash; US Department of the Interior (@Interior) <a href="https://twitter.com/Interior/status/463440424141459456?ref_src=twsrc%5Etfw">May 5, 2014</a></blockquote>
        </div>
    
    </div>
  2. Create a new service and dynamically load and initialize the widget inside the onAccept callback:

    im.run({
        services: {
            twitter: {
                onAccept: async (div, setIframe) => {
                    // Using cookieconsent v3
                    await CookieConsent.loadScript('https://platform.twitter.com/widgets.js');
    
                    // Make sure the "window.twttr" property exists
                    await im.childExists({childProperty: 'twttr'}) && await twttr.widgets.load(div);
    
                    // Make sure the "iframe" element exists
                    await im.childExists({parent: div}) && setIframe(div.querySelector('iframe'));
                },
    
                onReject: (iframe) => {
                    iframe && iframe.parentElement.remove();
                }
            }
        }
    })

It is highly recommended to set a fixed width and height to the main data-service div, to avoid the (awful) content jump effect when the iframe is loaded.

Placeholder for non-js browsers

You can set a placeholder visible only if javascript is disabled via a special div:

<div data-placeholder data-visible></div>

Example:

<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>

APIs

The plugin exposes the following methods:

Example usage:

// accept specific service only
im.acceptService('youtube');

// accept all services (for example if user has given full consent to cookies)
im.acceptService('all');

// reject specific service
im.rejectService('youtube');

// reject all services (for example when user opts out of cookies)
im.rejectService('all');

// get entire config object
const config = im.getConfig();

// get current state (enabled/disabled services)
const state = im.getState();

// state.services: Map<string, boolean>
// state.acceptedServices: string[]

Both acceptService and rejectService work the same way:

  1. set/erase cookie
  2. create/remove iframes

Configuration examples

Usage with CookieConsent [v1.2.0+]

You can use the onChange callback to detect when an iframe is loaded by the loadAllBtn button click event and notify CookieConsent to also update its state.

Example:

im.run({
    currLang: 'en',

    onChange: ({changedServices, eventSource}) => {

        if(eventSource.type === 'click') {
            // Retrieve all accepted services:
            // const allAcceptedServices = im.getState().acceptedServices;

            /**
             * 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: {
        // ...
    }
});

Note: the above example assumes that all services belong to the analytics category.

Available data-ratio

Horizontal aspect ratio:

Vertical aspect ratio:

License

Distributed under the MIT License. See LICENSE for more information.


Note

Not all services (example: twitch) allow automatic/easy thumbnail fetch.