Managing iframe Embedded Content Session State using enigma.js

Contributed by Daniel Pilla


In this tutorial, you are going to learn how to embed an iframe and monitor its session state with Qlik.


  • You are embedding Qlik content using iframes and need to display custom modals, add private labels to dialogs, or control messages when a user's session disconnects.
  • You've embedded Qlik into a portal and you want to keep the portal open for the duration of the user's session.

Monitoring iframe content from Qlik uses enigma.js, a JavaScript library with access to Qlik Sense session state information.



The code example in this tutorial provides a basic, fully functional web page. To use this example page, ensure you have the following:

  1. An existing Qlik Sense application Id and sheet Id to embed.
  2. A web integration id to prevent cross-site request attacks.
  3. A content security policy entry with the frame-ancestors directive added for the location of the web page that will be embedding the content.

To configure the code, the following variables from the sample code must be filled in:

const TENANT = '<tenant>.<region>';
const WEBINTEGRATIONID = '<web-integration-id>';
const APPID = '<app-id>';
const SHEETID = '<sheet-id>';
const IDENTITY = '<identity>';

Note: The IDENTITY > parameter is an arbitrary string to establish a separate session state. This can be used across iframes/varying methods of embedding to tie and/or separate sessions accordingly. It is an optional parameter included in this example for illustrative purposes.


The code in this sample ultimately does the following:

  1. Handles logging into Qlik.
  2. Fetches the CSRF token so that a websocket connection can be made to the engine.
  3. Connects to the application leveraging enigma.js.
  4. Establishes listeners on the websocket session closed and suspended events.
  5. Fetches the active theme of the application (this is purely bonus code showing the power of the engine).
  6. Renders the iframe.
  7. A timer is set to automatically close the engine websocket session in 10 seconds.
  8. The engine websocket session is closed, triggering the listener receive the closed event and illustrating the catch.
  9. The iframe is hidden and a custom message is displayed.

Sample code

The sample code showcases a few capabilities in one example, providing a boilerplate you can modify. Here are some of the features:

  • Automatically hides the iframe and displays a message inline.

  • Catch session events, such as, displaying a custom modal dialog or redirecting to another web page.

  • Handles browser support for third-party cookies. Refer to this article for more information.


    <script src=""></script>


    <div id="main">
        <div id="message"></div>
        <iframe id='qlik_frame' style='border:none;width:100%;height:900px;'></iframe>


        //    CONFIGURATION

        const TENANT = '<tenant>.<region>';
        const WEBINTEGRATIONID = '<web-integration-id>';
        const APPID = '<app-id>';
        const SHEETID = '<sheet-id>';
        const IDENTITY = '<identity>';

        //    MAIN

        (async function main() {
            const isLoggedIn = await qlikLogin();
            const qcsHeaders = await getQCSHeaders();
            const [session, enigmaApp] = await connectEnigma(qcsHeaders, APPID, IDENTITY);
            const theme = await getTheme(enigmaApp);
            renderSingleIframe('qlik_frame', APPID, SHEETID, theme, IDENTITY);
            const message = 'Session will be automatically closed in 10 seconds to showcase the handling.';
            document.getElementById('message').innerHTML = message; // remove this after testing
            setTimeout(() => { // remove this after testing
            }, "10000")

        //    LOGIN

        async function qlikLogin() {
            const loggedIn = await fetch(`https://${TENANT}/api/v1/users/me`, {
                mode: 'cors',
                credentials: 'include',
                headers: {
                    'qlik-web-integration-id': WEBINTEGRATIONID,
            if (loggedIn.status !== 200) {
                if (sessionStorage.getItem('tryQlikAuth') === null) {
                    sessionStorage.setItem('tryQlikAuth', 1);
                    window.location = `https://${TENANT}/login?qlik-web-integration-id=${WEBINTEGRATIONID}&returnto=${location.href}`;
                    return await new Promise(resolve => setTimeout(resolve, 10000)); // prevents further code execution
                } else {
                    const message = 'Third-party cookies are not enabled in your browser settings and/or browser mode.';
                    throw new Error(message);
            console.log('Logged in!');
            return true;

        async function getQCSHeaders() {
            const response = await fetch(`https://${TENANT}/api/v1/csrf-token`, {
                mode: 'cors',
                credentials: 'include',
                headers: {
                    'qlik-web-integration-id': WEBINTEGRATIONID

            const csrfToken = new Map(response.headers).get('qlik-csrf-token');
            return {
                'qlik-web-integration-id': WEBINTEGRATIONID,
                'qlik-csrf-token': csrfToken,


        async function connectEnigma(qcsHeaders, appId, identity) {
            const [session, app] = await getEnigmaSessionAndApp(appId, qcsHeaders, identity);
            return [session, app];

        async function getEnigmaSessionAndApp(appId, headers, identity) {
            const params = Object.keys(headers)
                .map((key) => `${key}=${headers[key]}`)

            return (async () => {
                const schema = await (await fetch('')).json();

                try {
                    return await createEnigmaAppSession(schema, appId, identity, params);
                catch {
                    // If the socket is closed immediately following the connection this
                    // could be due to an edge-case race condition where the newly created
                    // user does not yet have access to the app due to access control propagation.
                    // This bit of code will make another attempt after a 1.5 seconds.
                    const waitSecond = await new Promise(resolve => setTimeout(resolve, 1500));
                    try {
                        return await createEnigmaAppSession(schema, appId, identity, params);
                    catch (e) {
                        throw new Error(e);

        async function createEnigmaAppSession(schema, appId, identity, params) {
            const session = enigma.create({
                url: `wss://${TENANT}/app/${appId}?${params}`,
                identity: identity
            const enigmaGlobal = await;
            const enigmaApp = await enigmaGlobal.openDoc(appId);
            return [session, enigmaApp];


        async function getTheme(enigmaApp) {
            const createAppProps = await enigmaApp.createSessionObject({
                qInfo: {
                    qId: "AppPropsList",
                    qType: "AppPropsList"
                qAppObjectListDef: {
                    qType: "appprops",
                    qData: {
                        theme: "/theme"
            const appProps = await enigmaApp.getObject('AppPropsList');
            const appPropsLayout = await appProps.getLayout();
            const theme = appPropsLayout.qAppObjectList.qItems[0].qData.theme;
            return theme;


        function handleDisconnect(session) {
            session.on('closed', () => {
                const message = '<Your text here> Due to inactivity or loss of connection, this session has ended.';
                document.getElementById('qlik_frame').style.display = "none";
                document.getElementById('message').innerHTML = message; // replace with own handling

            session.on('suspended', () => {
                const message = '<Your text here> Due to loss of connection, this session has been suspended.';
                document.getElementById('qlik_frame').style.display = "none";
                document.getElementById('message').innerHTML = message;

            window.addEventListener('offline', () => {


        function renderSingleIframe(frameId, appId, sheetId, theme, identity) {
            const frameUrl = `https://${TENANT}/single/?appid=${appId}&sheet=${sheetId}&theme=${theme}&identity=${identity}&opt=ctxmenu,currsel`;
            document.getElementById(frameId).setAttribute('src', frameUrl); // replace with own handling



Was this page helpful?