Skip to main content
Announcements
YOUR OPINION MATTERS! Please take the Qlik Experience survey you received via email. Survey ends June 14.
cancel
Showing results for 
Search instead for 
Did you mean: 
pixlics12
Contributor III
Contributor III

How to embed Qlik App/Sheet on the website

I want to embed Qlik app/sheet on my website. There are apps on the Qlik, and the apps contains the sheets, which I want to embed for showing to website visitors/user. But users don't have access of Qlik, so I need to embed with some kind of authentication by code.

Before going on the code, I have following things created on the Qlik Management Console (QMC):

  1. Tenant host
  2. Added my domain in the Content Security Policy section and selected frame-ancestors in the Directive
  3. Web Integration Id created in Web Integration Configuration section
  4. Client Id and secret created in OAuth section
  5. Public key, Private key created by following this article: Create Signed Tokens for JWT Authorization
  6. After following above article, I also have Issuer, Key ID which I setup in the Identity Provider section

Following is my usecase

User first select an app from the dropdown, this list populated through the following API:

GET https://QLIK-TENANT/api/v1/items?resourceType=app
Header: Bearer <token generated with OAuth Client id and secret>

And it is working.

After that I need to show another dropdown for all the sheets of the selected app. But I am unable to fetch the list of sheets. I checked there is no REST API which can give me sheets by an app.

Question 1: How can I get sheet list of the app? I also plan to list the sheets using below JS code, but that is also not working.

Once user select app and sheet, then click on a button to Render sheet, and it should render that sheet on the webpage.

I tried using below piece of code (enigma js), referenced from here: Handle sheets in iframes with enigma.js

<html>
<head>
<script src="https://unpkg.com/enigma.js/enigma.min.js"></script>
</head>
<body>
    <div id="main">
        <div id="message"></div>
        <iframe id="qlik_frame" style="border:none;width:100%;height:900px;"></iframe>
    </div>
    <script type="text/javascript">
        // CONFIGURATION
        const TENANT = "xxxxxxxxxxxxx.xx.qlikcloud.com";
        const JWTENDPOINT = "https://example.com/api/v1/qlik/jwtToken"; // this endpoint create a JWT token using passed data and the private key mentioned in point #6 above
        const WEBINTEGRATIONID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; // mentioned in point #3 above
        const APPID = "411111e0-36f8-4e6d-b25d-f0xxxxxxxxca"; // got using the REST API mentioned above
        const IDENTITY = "xxxxxxxxxxxxxx"; // An arbitrary string to establish a separate session state.

        // MAIN
        (async function main() {
            const isLoggedIn = await qlikLogin();
            const qcsHeaders = await getQCSHeaders();
            const [session, enigmaApp] = await connectEnigma(qcsHeaders, APPID, IDENTITY);

            handleDisconnect(session);
            const theme = await getTheme(enigmaApp);
            const spaceId = (await getApp(APPID)).spaceId;
            const spaceType = await getSpaceType(spaceId);
            const sheets = await getSheetList(enigmaApp, spaceType);

            renderSingleIframe("qlik_frame", APPID, sheets[0].qInfo.qId, theme, IDENTITY);
        })();

        // LOGIN
        async function qlikLogin() {
            const loggedIn = await checkLoggedIn();
            if (loggedIn.status !== 200) {
                const $jwtPostData = {
                    kid: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", // mentioned in point #6 above
                    iss: "xxxxxxxxxxxxx.xx.qlikcloud.com", 
                    name: "?????????" // what it should be????
                };
                const tokenRes = await (await getJWTToken(JWTENDPOINT, $jwtPostData));
                const loginRes = await jwtLogin(tokenRes.body);

                if (loginRes.status != 200) {
                    const message = "Something went wrong while logging in.";
                    throw new Error(message);
                }
                const recheckLoggedIn = await checkLoggedIn();
                if (recheckLoggedIn.status !== 200) {
                    const message = "Third-party cookies are not enabled in your browser settings and/or browser mode.";
                    throw new Error(message);
                }
            }
            return true;
        }

        async function checkLoggedIn() {
            return await fetch(`https://${TENANT}/api/v1/users/me`, {
                mode: "cors",
                credentials: "include",
                headers: {
                    "qlik-web-integration-id": WEBINTEGRATIONID
                },
            })
        }

        // Get Method the JWT and use it to obtain Qlik Cloud session cookie.
        async function getJWTToken(jwtEndpoint, payloadData) {
            try {
                // Define the headers for the request, including the content type
                const headers = new Headers({
                    "Content-Type": "application/json",
                });

                // Create a request object with the specified method, headers, and body
                const requestOptions = {
                    method: "POST",
                    headers: headers,
                    mode: "cors",
                    body: JSON.stringify(payloadData), // Convert payloadData to JSON
                };

                // Use the fetch function to make a POST request to the specified endpoint
                const response = await fetch(jwtEndpoint, requestOptions);

                // Check if the response status code indicates success (e.g., 200 OK)
                if (response.ok) {
                    // Parse the response body as JSON, assuming it contains the JWT
                    const jwtData = await response.json();
                    return jwtData; // Return the JWT data
                } else {
                    // Handle HTTP error responses here if needed
                    throw new Error(`HTTP error! Status: ${response.status}`);
                }
            } catch (error) {
                // Handle any exceptions that may occur during the fetch request
                console.error("Error fetching JWT:", error);
                throw error;
            }
        }

        async function jwtLogin(token) {
            const authHeader = `Bearer ${token}`;
            return await fetch(`https://${TENANT}/login/jwt-session?qlik-web-integration-id=${WEBINTEGRATIONID}`, {
                credentials: "include",
                mode: "cors",
                method: "POST",
                headers: {
                    "Authorization": authHeader,
                    "qlik-web-integration-id": WEBINTEGRATIONID
                },
            })
        }

        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,
            };
        }

        // ENIGMA ENGINE CONNECTION
        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]}`)
                .join("&");

            return (async () => {
                const schema = await (await fetch("https://unpkg.com/enigma.js@2.7.0/schemas/12.612.0.json")).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({
                schema,
                url: `wss://${TENANT}/app/${appId}/identity/${identity}?${params}`,
                createSocket: url => new WebSocket(`wss://${TENANT}/app/${appId}/identity/${identity}?${params}`),
            });

            const enigmaGlobal = await session.open();
            const enigmaApp = await enigmaGlobal.openDoc(appId);
            return [session, enigmaApp];
        }

        // HANDLE ENGINE SESSION CLOSURE
        function handleDisconnect(session) {
            session.on("closed", () => {
                console.log("Due to inactivity or loss of connection, this session has ended.");
            });

            session.on("suspended", () => {
                console.log("Due to loss of connection, this session has been suspended.");
            });

            window.addEventListener("offline", () => {
                session.close();
            });
        }

        // GET QLIK APP (FOR SPACE ID)
        async function getApp(appId) {
            var url = new URL(`https://${TENANT}/api/v1/items?resourceType=app&resourceId=${appId}`);
            const response = await fetch(url, {
                method: "GET",
                mode: "cors",
                credentials: "include",
                headers: {
                    "Content-Type": "application/json",
                    "qlik-web-integration-id": WEBINTEGRATIONID,
                },
            })
            responseJson = await response.json();
            return responseJson.data[0];
        }

        // GET SPACE (FOR SPACE TYPE)
        async function getSpaceType(spaceId) {
            var url = new URL(`https://${TENANT}/api/v1/spaces/${spaceId}`);
            const response = await fetch(url, {
                method: "GET",
                mode: "cors",
                credentials: "include",
                headers: {
                    "Content-Type": "application/json",
                    "qlik-web-integration-id": WEBINTEGRATIONID,
                },
            })
            responseJson = await response.json();
            return responseJson.type;
        }

        // GET THEME
        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;
        }

        // GET SHEETS (WITH TYPE ADDED, E.G., BASE, COMMUNITY, PRIVATE)
        async function getSheetList(app, spaceType) {
            var sheets = await app.getObjects({
                "qOptions": {
                    "qTypes": [
                        "sheet"
                    ],
                    "qIncludeSessionObjects": false,
                    "qData": {}
                }
            })
            sheetsIncludingType = [];
            for await (const sheet of sheets) {
                var pushSheet = true;
                const sheetObject = await app.getObject(sheet.qInfo.qId);
                const sheetLayout = await sheetObject.getLayout();
                var isManaged = spaceType === "managed";
                var sheetTypeEnum = 1;
                var approved = sheet.qMeta.approved;
                var published = sheet.qMeta.published;
                const sheetTypeEnums = {
                    1: "Base",
                    2: "Community",
                    3: "Private"
                };
                if (!approved && !published) {
                    sheetTypeEnum = 3;
                } else if (!approved && published) {
                    if (isManaged) {
                        sheetTypeEnum = 2;
                    }
                }
                const sheetTypeObject = {
                    "sheetType": sheetTypeEnums[sheetTypeEnum],
                    "sheetTypeEnum": sheetTypeEnum
                };
                const mergedObject = {
                    ...sheetLayout,
                    ...sheetTypeObject
                };
                sheetsIncludingType.push(mergedObject)
            }
            sheetsIncludingType.sort((a, b) => (a.sheetTypeEnum - b.sheetTypeEnum || a.rank - b.rank))
            return sheetsIncludingType;
        }

        // HELPER FUNCTION TO GENERATE IFRAME
        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);
        }
    </script>
</body>
</html>

But when I tried to run, it is not working and returned following JS error in console, related to Socket:

console.log.png

 

Following is the network logs when I tried to run this code:

network.log.png

Question 2: How to embed sheet/app? In above code, Am I doing something wrong? Or if there are any other way to embed, please suggest me. Because I doubt, that it will need to create a lot of resources on the QMC. So there should be some API, to authenticate user, get token and use token to get embed code for the sheet.

Please help me. Any suggestion will also be helpful.

Thank you

Labels (3)
24 Replies
pixlics12
Contributor III
Contributor III
Author

@alex_colombo I am able to view 300+ apps, and there sheets too on the Qlik.

alex_colombo
Employee
Employee

If you can see sheet you should be able to embed them. Could you please share screenshots of error or errors in developer browser tool?

pixlics12
Contributor III
Contributor III
Author

I got the following error when tried to embed another sheets:

pixlics12_0-1710945896677.png


Also, one thing I want to share regarding this. I checked that there are a lot of Spaces exists on the Qlik. I don't have access of any of them.
For experiment, I added my user in one of the Space and then tried to embed the App/Sheet of this Space, then it was working.

So is it mandatory that the App/Sheet and the user (on behalf of I am embedding) must belong to the same Space?

alex_colombo
Employee
Employee

For emebdding any Qlik resources, current user must have the correct permission for accessing the space and the app. For doing this you have to check in Management console if the user has the correct right for accessing each space

pixlics12
Contributor III
Contributor III
Author

Thank you @alex_colombo, but

  1. Which permission is required to embed the App/Sheet in an Space?
  2. Currently, the user got following popup on each run of embed web page. As I understood, this happened because I used same user's email to generate JWT token. So Qlik treat that this user is newly added and logged in first time. But my customers are not happy with this scenario.

Can you please help me on these 2 points?
I am close to deploy the embed functionality on prod server.

pixlics12_0-1710953914550.png

Thank You