@qlik/sdk
TypeScript icon, indicating that this package has built-in type declarations

0.25.0 • Public • Published

qlik-sdk-typescript

The Qlik Platform SDKs are a suite of tools, libraries and, documentations that simplifies building high-quality and performant applications on top of the Qlik Sense Platform.



Getting started

Node.js needs to be installed to use @qlik/sdk in a node.js project. The node.js version is important, please use a version later than or equal to 18.10.0. If you are using an earlier version of node.js then make sure that the fetch function is available. Please see the fetch section for details: Fetch With node.js installed, create a node.js project Install qlik/sdk and try the API keys example.

Install

npm i @qlik/sdk
### or yarn
yarn add @qlik/sdk

Authentication options

API keys

An API key is a token representing a user in your tenant. Anyone may interact with the platform programmatically using the API key. The token contains the user context, respecting the access control privileges the user has in your tenant. More info can be found on Qlik Dev Portal.

For a step-by-step guide on how to get an API key for your tenant, check this tutorial.

import { Auth, AuthType, Config } from '@qlik/sdk';

const config: Config =  {
  authType: AuthType.APIKey,
  host: 'my-tenant.qlikcloud.com',
  apiKey: '<apiKey>',
};

const auth = new Auth(config);
await auth.rest('/users/me');

Web integration ID

A web integration ID will allow you to handle CORS requests in Qlik Sense SaaS, which by default does not allow third-party domains to interact with your tenant APIs. More info on Qlik Dev Portal.

For a step-by-step guide on how to get a web integration ID for your tenant, check Qlik Help.

import { Auth, AuthType, Config } from '@qlik/sdk';

const config: Config =  {
  authType: AuthType.WebIntegration,
  host: 'my-tenant.qlikcloud.com',
  webIntegrationId: '<webintegrationId>',
  autoRedirect: true, // default false
};

const auth = new Auth(config);

if(!auth.isAuthenticated()){ // checks the "/users/me" endpoint
  auth.authenticate(); // redirects to IDP login page
}

JWT Auth

A digitally signed JSON web token that can be verified and trusted using a public / private key pair. More info on Qlik Dev Portal.

For a step-by-step guide on how to create a signed token for JWT Authorization for your tenant, check Qlik Dev Portal

import { Auth, AuthType, Config } from '@qlik/sdk';

const config: Config =  {
  authType: AuthType.JWTAuth,
  host: 'my-tenant.qlikcloud.com',
  webIntegrationId: '<webintegrationId>',
  fetchToken: () => Promise.resolve('<signedToken>'),
};

const auth = new Auth(config);

await auth.getSessionCookie(); // function helper for setting the session cookies.

OAuth2

OAuth is a standard security protocol for authorization and delegation. It allows third party applications to access API resources without disclosing the end-user credentials.

For a step-by-step guide on how to create an OAuth client for your tenant, check Creating and managing OAuth clients

import { Auth, AuthType, Config } from '@qlik/sdk';

const config: Config =  {
  authType: AuthType.OAuth2,
  host: 'my-tenant.qlikcloud.com',
  clientId: '<clientId>',
  clientSecret: '<clientSecret>',
  redirectUri: '<redirectUri>',
  scopes: '<scopes>',
};

const auth = new Auth(config);

// in single page application, order matters, we first need to check if the page load is from a redirect
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
if (code) { // it's has been redirected
  await auth.authorize(window.location.href); // exchanges the credentials for a token
  // ....
  // websocket url for Enigma.js
  const wsUrl = await auth.generateWebsocketUrl(appId);
}
// ....
if (! await auth.isAuthorized()) { // check is the current web-app has already been authorized.
  const { url } = await auth.generateAuthorizationUrl(); // generate the url needed for for the OAuth exchange token flow
  window.location = url;
}

// other available methods for the OAuth flow are:
await auth.deauthorize();
await auth.refreshToken();

Fetching Delta values with Engine RPC

Delta member in app open config or rpc function can be used to change behaviour of engine response. By default this is set to true for bandwidth reducing, so engine only sends changes in object and default delta interceptors evaluate the object. On setting this property false while opening an app or while setting up the session, engine will respond with entire object on each request.

Example to set delta false:

    const session = await app.open({ delta: false });

OR

    const session = await auth.rpc(appId, { delta: false });

Fetch

The global.fetch function is assumed to be available. If it is not availble then it can be added using:

ES6-Modules

  1. Add node-fetch as dependency
  2. Import by overriding global fetch global.fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));

CommonJS

  1. Add cross-fetch as dependency
  2. Use this code snippet:
const fetch = require('cross-fetch');
global.fetch = fetch

Object events

It is possible to register object event listener functions using the syntax: .on('event-name', listener). In this example the object changed event is listened to.

const qlik = new Qlik({
  authType: AuthType.APIKey,
  host: "",
  apiKey: "",
});

// Create a session app
const randomId = Math.random().toString(32).substring(3);
const sessionAppId = `SessionApp_${randomId}`;
const app = await qlik.apps.createSessionApp(sessionAppId);

// Open a websocket session
const session = await app.open();

// Set a script in the app
const script = `
TempTable:
Load
RecNo() as ID,
Rand() as Value
AutoGenerate 100
`;
await app.setScript(script);

// Create an object with a hypercube using fields in the data model
const properties = {
  qInfo: {
    qType: 'my-straight-hypercube',
  },
  qHyperCubeDef: {
    qDimensions: [
      {
        qDef: { qFieldDefs: ['ID'] },
      },
    ],
    qMeasures: [
      {
        qDef: { qDef: '=Sum(Value)' },
      },
    ],
    qInitialDataFetch: [
      {
        qHeight: 5,
        qWidth: 2,
      },
    ],
  },
};
const hypercube = await app.createObject(properties);
await hypercube.getLayout();

// Register an event listener for change events
hypercube.on('changed', () => {
  console.log('changed');
});

// Do a reload of the app
await app.doReload();

// Close session
await session.close();

Paginated responses

Some endpoints have paginated responses. A paginated response means that a list of elements are returned together with a link to more elements of that type. A purpose of paginated responses is to limit the number of returned elements at a time. A get request on /items will return a paginated response. The qlik-sdk handles paginated responses.

Examples

Express OAuth

const express = require('express');
const path = require('path');

const app = express();

require('dotenv').config({ path: path.join(__dirname, '.env') });

const Qlik = require('@qlik/sdk').default;
const { AuthType } = require('@qlik/sdk');

const qlik = new Qlik({
  authType: AuthType.OAuth2,
  host: process.env.OAUTH_M2M_WEB_QCS_SERVER,
  clientId: process.env.OAUTH_M2M_WEB_CLIENT_ID,
  clientSecret: process.env.OAUTH_M2M_WEB_CLIENT_SECRET,
  redirectUri: 'http://localhost:3000/login/callback',
  scopes: ['offline_access'],
});
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'hbs');

app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));

let status = '';

app.get('/', async (req, res) => {
  try {
    const userMe = await qlik.users.getMe();
    status = `Logged in as: ${userMe.name}`;
    res.render(
      'index',
      { loggedIn: true, status },
    );
  } catch (err) {
    res.render('index', { loggedIn: false, status: 'Not logged in' });
  }
});

app.get('/login', async (req, res) => {
  const { url } = await qlik.auth.generateAuthorizationUrl();
  res.redirect(301, url);
});

app.get('/login/callback', async (req, res) => {
  try {
    await qlik.auth.authorize(
      (new URL(req.url, `http://${req.headers.host}`)).href,
    );
    res.redirect(301, '/');
  } catch (err) {
    res.sendStatus(401);
    res.render('error', { error: err });
  }
});

app.get('/logout', async (req, res) => {
  try {
    await qlik.auth.deauthorize();
    res.redirect(301, '/');
  } catch (err) {
    res.sendStatus(500);
    res.render('error', { error: err });
  }
});

app.get('/refresh', async (req, res) => {
  try {
    await qlik.auth.refreshToken();
    res.redirect(301, '/');
  } catch (err) {
    res.sendStatus(500);
    res.render('error', { error: err?.message || err });
  }
});

app.get('/websocket', async (req, res) => {
  const randomId = Math.random().toString(32).substring(3);
  const appId = `SessionApp_${randomId}`;
  try {
    // Open a websocket for a session app using RpcClient
    const rpcSession = await qlik.auth.rpc(appId);
    try {
      await rpcSession.open();
      const {
        result: {
          qReturn: {
            qHandle: appHandle,
          },
        },
      } = await rpcSession.send({
        handle: -1,
        method: 'GetActiveDoc',
        params: [],
      });
      await rpcSession.send({
        handle: appHandle,
        method: 'SetScript',
        params: { qScript: 'Load RecNo() as N autogenerate(10);' },
      });
      await rpcSession.send({
        handle: appHandle,
        method: 'DoReload',
        params: [],
      });
      const evalResult = await rpcSession.send({
        handle: appHandle,
        method: 'Evaluate',
        params: ['SUM([N])'],
      });
      res.render(
        'index',
        { evalResult: evalResult.result.qReturn, loggedIn: true, status },
      );
      await rpcSession.close();
      console.log('rpcSession closed');
    } catch (err) {
      console.log('rpcSession error:', err);
    } finally {
      await rpcSession.close();
    }
  } catch (err) {
    res.sendStatus(500);
    res.render('error', { error: err?.message || err });
  }
});

module.exports = app;

Node.js

const path = require('path');
const process = require('process');
require('dotenv').config({ path: path.join(__dirname, '.env') });
const { AuthType } = require('@qlik/sdk');

const Qlik = require('@qlik/sdk').default;

const envVars = ['QCS_SERVER', 'QCS_API_KEY'];
for (let i = 0; i < envVars.length; i += 1) {
  if (!(envVars[i] in process.env)) {
    console.log(`Missing environment variable: ${envVars[i]}`);
    process.exit(1);
  }
}

(async () => {
  const qlik = new Qlik({
    authType: AuthType.APIKey,
    host: process.env.QCS_SERVER,
    apiKey: process.env.QCS_API_KEY,
  });

  const user = await qlik.users.getMe();
  console.log(`Logged in as: ${user.name}`);

  // create app
  const app = await qlik.apps.create();

  // set attribute - app name
  await app.set({ attributes: { name: 'example' } });

  // open app and run commands
  await app.open();
  const script = 'Load RecNo() as N autogenerate(100);';
  await app.setScript(script);
  await app.doReload();
  const evalResult = await app.evaluate('SUM([N])');
  console.log(`Eval result: ${evalResult}`);

  // delete app
  await app.delete();
})();

Node16

const path = require('path');
const process = require('process');
require('dotenv').config({ path: path.join(__dirname, '.env') });
const { Auth, AuthType } = require('@qlik/sdk');

const fetch = require('cross-fetch');

global.fetch = fetch;

const envVars = ['QCS_SERVER', 'QCS_API_KEY'];
for (let i = 0; i < envVars.length; i += 1) {
  if (!(envVars[i] in process.env)) {
    console.log(`Missing environment variable: ${envVars[i]}`);
    process.exit(1);
  }
}

const auth = new Auth({
  authType: AuthType.APIKey,
  host: process.env.QCS_SERVER,
  apiKey: process.env.QCS_API_KEY,
});

(async () => {
  // get user
  try {
    const res = await auth.rest('/users/me');
    if (res.status !== 200) {
      console.log('Failed to get /users/me');
      process.exit(1);
    }
    const userData = await res.json();
    console.log(`Logged in as: ${userData.name}`);
  } catch (error) {
    console.log('Error on get /users/me');
    console.log(error);
  }
})();

Pagination

const path = require('path');
const process = require('process');
require('dotenv').config({ path: path.join(__dirname, '.env') });
const { AuthType } = require('@qlik/sdk');

const Qlik = require('@qlik/sdk').default;

const envVars = ['QCS_SERVER', 'QCS_API_KEY'];
for (let i = 0; i < envVars.length; i += 1) {
  if (!(envVars[i] in process.env)) {
    console.log(`Missing environment variable: ${envVars[i]}`);
    process.exit(1);
  }
}

(async () => {
  const qlik = new Qlik({
    authType: AuthType.APIKey,
    host: process.env.QCS_SERVER,
    apiKey: process.env.QCS_API_KEY,
  });

  const user = await qlik.users.getMe();
  // Get all app items by setting resourceType to 'app',
  // created by current user: createdByUserId: user.id
  // The response from getItems will only include the first (about 10) elements
  const items = await qlik.items.getItems(
    { resourceType: 'app', createdByUserId: user.id },
  );

  // The elements can be accessed like an array
  const firstItem = items[0];
  console.log(`First item name = ${firstItem.name}`);

  // The items.pagination iterator can be used
  //  to get all the elements
  // When looping through items.pagination it will
  // start at the first element returned from the getItems request.
  // Additional get items requests will be done as needed.
  const allItemNames = [];
  for await (const item of items.pagination) {
    allItemNames.push(item.name);
  }

  // All elements are now accessible through the array index
  const lastItem = items[items.length - 1];
  console.log(`Last item name = ${lastItem.name}`);

  console.log(allItemNames);
})();

Single Page Application OAuth

import { Auth, AuthType } from '@qlik/sdk';
import enigma from 'enigma.js';
import schema from 'enigma.js/schemas/12.1477.0.json';

(async () => {
  // Create a ".env"-file in this folder based on the .env.example file
  // and fill in the values
  const auth = new Auth({
    authType: AuthType.OAuth2,
    host: process.env.QCS_SERVER,
    clientId: process.env.OAUTH_SPA_CLIENT_ID,
    redirectUri: process.env.OAUTH_REDIRECT_URI,
  });
  const authorizeBtnEl = document.getElementById('authorize');
  const deauthorizeBtnEl = document.getElementById('deauthorize');
  const status = document.getElementById('status');
  const setStatus = async (loggedIn) => {
    if (loggedIn) {
      authorizeBtnEl.setAttribute('disabled', true);
      deauthorizeBtnEl.removeAttribute('disabled');
      const user = await auth.rest('/users/me').then((resp) => resp.json());
      status.innerHTML = `Logged in as: ${user.name}`;
    } else {
      authorizeBtnEl.removeAttribute('disabled');
      deauthorizeBtnEl.setAttribute('disabled', true);
      status.innerHTML = 'Not authorized';
    }
  };

  if (await auth.isAuthorized()) {
    await setStatus(true);
  } else {
    const urlParams = new URLSearchParams(window.location.search);
    const code = urlParams.get('code');
    if (code) { // it's has been redirected
      await auth.authorize(window.location.href);
      const url = new URL(window.location);
      url.searchParams.delete('code');
      url.searchParams.delete('state');
      await setStatus(true);
      window.history.replaceState(null, null, url);
    } else {
      setStatus(false);
    }
  }

  authorizeBtnEl.addEventListener('click', async (e) => {
    e.preventDefault();
    // the verifier must be stored because,
    // the page is redirected to the authorization url,
    // from the authorization page redirected back to here,
    // therefore the original auth instance in unavailable
    const { url } = await auth.generateAuthorizationUrl();
    window.location = url;
  });
  authorizeBtnEl.setAttribute('clickable', 'true');

  deauthorizeBtnEl.addEventListener('click', async (e) => {
    e.preventDefault();
    await auth.deauthorize();
    setStatus(false);
  });

  const randomId = Math.random().toString(32).substring(3);
  const appId = `SessionApp_${randomId}`;

  // Open a websocket for a session app using enigma.js
  const enigmaBtnEl = document.getElementById('enigma');

  enigmaBtnEl.onclick = async () => {
    const evalResult = document.getElementById('evalResult');
    evalResult.hidden = true;
    evalResult.innerText = '';
    let wsUrl = false;
    try {
      wsUrl = await auth.generateWebsocketUrl(appId);
    } catch (error) {
      // eslint-disable-next-line no-console
      console.log(error);
      wsUrl = false;
    }
    if (!wsUrl) {
      return;
    }

    // create an enigma instance
    const global = await enigma
      .create({
        schema,
        url: wsUrl,
      })
      .open();
    const app = await global.getActiveDoc();
    const script = 'Load RecNo() as N autogenerate(100);';
    await app.setScript(script);
    await app.doReload();
    const result = await app.evaluate('SUM([N])');
    evalResult.innerText = result;
    evalResult.hidden = false;
  };

  // Open a websocket for a session app using RpcClient
  const rpcClientBtnEl = document.getElementById('websocket');
  rpcClientBtnEl.onclick = async () => {
    const evalResult = document.getElementById('evalResult');
    evalResult.hidden = true;
    evalResult.innerText = '';
    const rpcSession = await auth.rpc(appId);
    let res;
    try {
      await rpcSession.open();
      const {
        result: {
          qReturn: {
            qHandle: appHandle,
          },
        },
      } = await rpcSession.send({
        handle: -1,
        method: 'GetActiveDoc',
        params: [],
      });
      await rpcSession.send({
        handle: appHandle,
        method: 'SetScript',
        params: { qScript: 'Load RecNo() as N autogenerate(150);' },
      });
      await rpcSession.send({
        handle: appHandle,
        method: 'DoReload',
        params: [],
      });
      res = await rpcSession.send({
        handle: appHandle,
        method: 'Evaluate',
        params: ['SUM([N])'],
      });
      await rpcSession.close();
      console.log('rpcSession closed');
    } catch (err) {
      console.log('rpcSession error:', err);
    } finally {
      await rpcSession.close();
    }
    evalResult.innerText = res.result.qReturn;
    evalResult.hidden = false;
  };
})();

Web App

import enigma from 'enigma.js';
import schema from 'enigma.js/schemas/12.1477.0.json';
import { Auth, AuthType } from '@qlik/sdk';

(async () => {
  // Create a ".env"-file in the web-app folder based on the .env.example file
  // and fill in the values
  const webIntegrationId = process.env.TEST_WEB_INT_ID;
  const tenantUri = process.env.QCS_SERVER;
  if (!webIntegrationId) {
    console.log('Missing webIntegrationId');
    return;
  }
  if (!tenantUri) {
    console.log('Missing tenantUri');
    return;
  }

  /** Set autoRedirect to false in order to try the
   * authenticate and deauthenticate functions */
  const config = {
    host: tenantUri,
    authType: AuthType.WebIntegration,
    webIntegrationId,
    autoRedirect: false,
  };
  const auth = new Auth(config);

  const status = document.getElementById('status');
  const wsUrlElem = document.getElementById('wsUrl');
  status.innerText = 'Not logged in';

  const authenticateButton = document.getElementById('authenticateButton');
  authenticateButton.onclick = () => {
    auth.authenticate();
  };

  const deAuthenticateButton = document.getElementById('deAuthenticateButton');
  deAuthenticateButton.onclick = () => {
    auth.deauthenticate();
  };

  const isLoggedIn = await auth.isAuthenticated();
  if (isLoggedIn) {
    status.innerText = 'Logged in as: ...';
    const meRes = await auth.rest('/users/me');
    if (!meRes.ok) {
      return;
    }
    const me = await meRes.json();
    status.innerText = `Logged in as: ${me.name}`;
    authenticateButton.classList.add('hidden');
    deAuthenticateButton.classList.remove('hidden');

    const randomId = Math.random().toString(32).substring(3);
    const appId = `SessionApp_${randomId}`;

    const rpcSession = await auth.rpc(appId);
    try {
      await rpcSession.open();
      const {
        result: {
          qReturn: {
            qHandle: appHandle,
          },
        },
      } = await rpcSession.send({
        handle: -1,
        method: 'GetActiveDoc',
        params: [],
      });
      await rpcSession.send({
        handle: appHandle,
        method: 'SetScript',
        params: { qScript: 'Load RecNo() as N autogenerate(10);' },
      });
      await rpcSession.send({
        handle: appHandle,
        method: 'DoReload',
        params: [],
      });
      const res = await rpcSession.send({
        handle: appHandle,
        method: 'Evaluate',
        params: ['SUM([N])'],
      });
      const evalResult = document.getElementById('evalResult');
      evalResult.innerText = res.result.qReturn;
      evalResult.hidden = false;
      await rpcSession.close();
      console.log('rpcSession closed');
    } catch (err) {
      console.log('rpcSession error:', err);
    } finally {
      await rpcSession.close();
    }
    const wsUrl = await auth.generateWebsocketUrl(appId);
    wsUrlElem.innerText = wsUrl;

    // create an enigma instance
    const global = await enigma
      .create({
        schema,
        url: wsUrl,
      })
      .open();
    const app = await global.getActiveDoc();
    const script = 'Load RecNo() as N autogenerate(100);';
    await app.setScript(script);
    await app.doReload();
    const result = await app.evaluate('SUM([N])');
    const enigmaEvalResult = document.getElementById('enigmaEvalResult');
    enigmaEvalResult.innerText = result;
    enigmaEvalResult.hidden = false;
  }
})();

Webpack App

import enigma from 'enigma.js';
import schema from 'enigma.js/schemas/12.936.0.json';
import { Auth, AuthType } from '@qlik/sdk';

(async () => {
  // Create a ".env"-file in the web-app folder based on the .env.example file
  // and fill in the values
  const webIntegrationId = process.env.TEST_WEB_INT_ID;
  const tenantUri = process.env.QCS_SERVER;
  if (!webIntegrationId) {
    console.log('Missing webIntegrationId');
    return;
  }
  if (!tenantUri) {
    console.log('Missing tenantUri');
    return;
  }

  /** Set autoRedirect to false in order to try the
   * authenticate and deauthenticate functions */
  const config = {
    host: tenantUri,
    authType: AuthType.WebIntegration,
    webIntegrationId,
    autoRedirect: false,
  };
  const auth = new Auth(config);

  const status = document.getElementById('status');
  const wsUrlElem = document.getElementById('wsUrl');
  status.innerText = 'Not logged in';

  const authenticateButton = document.getElementById('authenticateButton');
  authenticateButton.onclick = () => {
    auth.authenticate();
  };

  const deAuthenticateButton = document.getElementById('deAuthenticateButton');
  deAuthenticateButton.onclick = () => {
    auth.deauthenticate();
  };

  const isLoggedIn = await auth.isAuthenticated();
  if (isLoggedIn) {
    status.innerText = 'Logged in as: ...';
    const meRes = await auth.rest('/users/me');
    if (!meRes.ok) {
      return;
    }
    const me = await meRes.json();
    status.innerText = `Logged in as: ${me.name}`;
    authenticateButton.classList.add('hidden');
    deAuthenticateButton.classList.remove('hidden');

    const randomId = Math.random().toString(32).substring(3);
    const appId = `SessionApp_${randomId}`;

    const rpcSession = await auth.rpc(appId);
    try {
      await rpcSession.open();
      const {
        result: {
          qReturn: {
            qHandle: appHandle,
          },
        },
      } = await rpcSession.send({
        handle: -1,
        method: 'GetActiveDoc',
        params: [],
      });
      await rpcSession.send({
        handle: appHandle,
        method: 'SetScript',
        params: { qScript: 'Load RecNo() as N autogenerate(10);' },
      });
      await rpcSession.send({
        handle: appHandle,
        method: 'DoReload',
        params: [],
      });
      const res = await rpcSession.send({
        handle: appHandle,
        method: 'Evaluate',
        params: ['SUM([N])'],
      });
      const evalResult = document.getElementById('evalResult');
      evalResult.innerText = res.result.qReturn;
      evalResult.hidden = false;
      await rpcSession.close();
      console.log('rpcSession closed');
    } catch (err) {
      console.log('rpcSession error:', err);
    } finally {
      await rpcSession.close();
    }
    const wsUrl = await auth.generateWebsocketUrl(appId);
    wsUrlElem.innerText = wsUrl;

    // create an enigma instance
    const global = await enigma
      .create({
        schema,
        url: wsUrl,
      })
      .open();
    const app = await global.getActiveDoc();
    const script = 'Load RecNo() as N autogenerate(100);';
    await app.setScript(script);
    await app.doReload();
    const result = await app.evaluate('SUM([N])');
    const enigmaEvalResult = document.getElementById('enigmaEvalResult');
    enigmaEvalResult.innerText = result;
    enigmaEvalResult.hidden = false;
  }
})();

/@qlik/sdk/

    Package Sidebar

    Install

    npm i @qlik/sdk

    Weekly Downloads

    852

    Version

    0.25.0

    License

    MIT

    Unpacked Size

    2.52 MB

    Total Files

    160

    Last publish

    Collaborators

    • nilzona_user
    • qlikossbuild
    • peol