Socket
Socket
Sign inDemoInstall

@qlik/sdk

Package Overview
Dependencies
17
Maintainers
3
Versions
36
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

    @qlik/sdk

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


Version published
Weekly downloads
1.4K
decreased by-10.35%
Maintainers
3
Created
Weekly downloads
 

Readme

Source

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 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();

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.

const queryParams = {}
const maxItems = 100
const items = await qlik.items.getItems(queryParams, maxItems);
// The response from getItems will only include the first (about 10) elements
// The elements can be accessed like an array
const item0 = items[0]
const all100ItemNames = [];
// The items.pagination iterator can be used to get all the elements up until max is reached.
// 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.
for await (const item of items.pagination) {
  all100ItemNames.push(item.names);
}
// All elements are now accessible through the array index
const item99 = items[99]

Changelog

Detailed changes for each release are documented in the release notes.

Contributing

Please make sure to read and follow our Code of conduct

Bugs

Bugs can be reported by adding issues in the repository. Please use the Bug Report template.

Features

Features can also be reported by adding issues in the repository. Please use the Feature Request template.

Developing

# install dependencies
yarn

# lint - also includes TSDoc linting (warn)
yarn lint
# lint with auto fix
yarn lint:fix

# run tests
yarn test
yarn test:unit
yarn test:integration

# generate documentation (typedoc) under `/docs`
yarn docs

# run build
yarn build
# re-build on file change - useful during development
yarn build:watch
Test

To run specific jest test files use testRegex

Environment variables

Copy .env.example to .env and fill in the values

Releasing

To create a release of the sdk do the following steps

  1. Check first that the documentation is updated (run yarn docs)
  2. Update the CHANGELOG.md with the changes compared to previous release.
  3. Change version in package.json.
  4. Open a Pull Request towards main branch with the changes from previous steps. Merge when approved.
  5. Create a github release for the new version.

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();
})();

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.OAUTH_QCS_SERVER,
    clientId: process.env.OAUTH_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;
  }
})();

Keywords

FAQs

Last updated on 21 Feb 2023

Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc