Skip to content
Snippets Groups Projects
viamapi-iframe.js 82.99 KiB
import "../lib/textencoder.polyfill";
import { parseSMIME, prepareVCardParts } from "../utilities/emailUtilities";
import {
  stringToUtf8ByteArray,
  utf8ByteArrayToString,
  stringToUtf8Base64,
  utf8Base64ToString,
  base64ToByteArray,
  byteArrayToBase64,
  hexStringToUtf8ByteArray
} from "../utilities/stringUtilities";
import { extractMessageID } from "../helpers/mailparser";

const Penpal = require("penpal").default;

import {
  createDeviceHash,
  destroyIdentityFromLocalStorage,
  encodeResponse,
  listIdentitiesFromLocalStorage,
  makeid
} from "../utilities/appUtility";
import { LOGIN_MODES } from "../constants/authentication";
import {
  CertificateData,
  ImageData,
  createOneTimePassportCertificate,
  createPassportCertificate,
  decryptMessage,
  encryptMessage,
  parseCertificate,
  signEmail,
  verifySMIME
} from "../utilities/signingUtilities";
import { signPdf } from "../utilities/pdfUtilities";
import CryptoData from "../CryptoData";
import Identity from "../Identity";
import {
  STATUS_DEVICE_REVOKED,
  STATUS_USER_NOT_ACTIVATED,
  STATUS_USER_BLOCKED
} from "../constants/statuses";
import generateQrCode from "../utilities/generateQrCode";
import {
  generateRecoveryKey,
  getRecoveryKeyShares,
  checkRecoveryKeyCombine,
  encryptShare
} from "../utilities/secrets";

const penpalMethods = require("../../temp/penpal-methods").default;
const WopiAPI = require("./wopiapi-iframe");
const CollaboraAPI = require("./collaboraapi-iframe");
const ViamAPI = require("../../temp/viamapi");

const identityColors = ["#994392", "#cb0767", "#e51d31", "#ec671b", "#fab610"];

function getNextColor() {
  let colorIndex = localStorage.getItem("colorIndex");
  if (colorIndex == null || colorIndex === "") {
    colorIndex = 0;
  }

  const color = identityColors[colorIndex];

  colorIndex++;

  colorIndex = colorIndex % identityColors.length;

  localStorage.setItem("colorIndex", colorIndex);

  return color;
}

function setKeyForUUID(uuid, key) {
  const storedIdentityForUuid = localStorage.getItem("keyperuuid/" + uuid);
  if (
    storedIdentityForUuid !== key &&
    storedIdentityForUuid != null &&
    storedIdentityForUuid !== ""
  ) {
    destroyIdentityFromLocalStorage(storedIdentityForUuid);
  }

  localStorage.setItem("keyperuuid/" + uuid, key);
}

function getColorForIdentity(key) {
  let storedColor = localStorage.getItem("colors/" + key);

  if (storedColor == null || storedColor === "") {
    storedColor = getNextColor();
    localStorage.setItem("colors/" + key, storedColor);
  }

  return storedColor;
}

function setIdentityInLocalStorage(identityToStore, extendKey = true) {
  let pinCode = identityToStore.pinCode;
  const serializedIdentity = JSON.stringify(identityToStore);
  const key = identityToStore.authentication.publicKey;

  if (pinCode == null || pinCode === "") {
    pinCode = getPincode(key);
  }

  if (pinCode == null || pinCode === "") {
    return null;
  }

  return encryptMessage(serializedIdentity, pinCode, "identity").then(
    encryptedIdentity => {
      let success = true;
      if (extendKey === true) {
        success = extendPinCodeTtl(key, pinCode);
      }
      if (success === true) {
        localStorage.setItem(key, encryptedIdentity);
        const serializedIdentitiesList = localStorage.getItem("identities");
        const identities = JSON.parse(serializedIdentitiesList);
        identities[key] = true;

        localStorage.setItem("identities", JSON.stringify(identities));
      } else {
        console.log("Can not extend pincode ttl");
      }
    }
  );
}

function getProfileData(identity) {
  return new Penpal.Promise(executeResultUpper => {
    executeRestfulFunction(
      "private",
      viamApi,
      viamApi.identityGetIdentityProfileData,
      null
    ).then(executeResult => {
      if (executeResult.code === "200") {
        const listItem = {};

        listItem.identityColor = getColorForIdentity(
          identity.authentication.publicKey
        );
        listItem.initials = executeResult.data.initials;

        if (listItem.initials === null || listItem.initials === "") {
          listItem.initials = "JD";
        }
        localStorage.setItem(
          "profiles/" + identity.authentication.publicKey,
          JSON.stringify(listItem)
        );
        executeResultUpper(listItem);
      } else {
        executeResultUpper({});
      }
    });
  });
}

async function getIdentityFromLocalStorage(key, pinCode, extendTtl = true) {
  const encryptedIdentity = localStorage.getItem(key);

  if (!encryptedIdentity) {
    console.log("No such identity for public key");
    return null;
  }

  const serializedIdentity = await decryptMessage(encryptedIdentity, pinCode);

  const identity = new Identity(serializedIdentity);

  if (extendTtl) {
    const success = extendPinCodeTtl(key, pinCode);
    if (!success) {
      console.log("Can not extend pincode ttl");
      return null;
    }
  }

  return identity;
}

function extendPinCodeTtl(key, pinCode) {
  if (pinCode == null || pinCode === "") {
    const now = new Date();
    const nowMillis = now.getTime();
    const ttl = window.sessionStorage.getItem("pincodettls/" + key);
    if (ttl == null || ttl === "" || nowMillis >= parseInt(ttl)) {
      clearPinCodeTtl(key);
      return false;
    } else {
      const ttl = now.getTime() + 4 * 60 * 60 * 1000;
      window.sessionStorage.setItem("pincodettls/" + key, ttl);
    }
  } else {
    const now = new Date();
    const ttl = now.getTime() + 4 * 60 * 60 * 1000;
    window.sessionStorage.setItem("pincodettls/" + key, ttl);
    window.sessionStorage.setItem("pincodes/" + key, pinCode);
  }

  return true;
}

window.extendPinCodeTtl = extendPinCodeTtl;

function clearPinCodeTtl(key) {
  window.sessionStorage.removeItem("pincodettls/" + key);
  window.sessionStorage.removeItem("pincodes/" + key);
}

function getPincode(key) {
  const now = new Date();
  const nowMillis = now.getTime();
  const ttl = window.sessionStorage.getItem("pincodettls/" + key);
  if (ttl == null || ttl === "") {
    return null;
  } else {
    if (nowMillis >= parseInt(ttl)) {
      clearPinCodeTtl(key);
      return null;
    } else {
      return window.sessionStorage.getItem("pincodes/" + key);
    }
  }
}

function createEvent(actionId, type, payloads) {
  return {
    actionID: actionId,
    type,
    stamp: new Date().getTime(),
    payloads
  };
}

const destroyAuthentication = () => {
  const authenticationPublicKey = localStorage.getItem("authenticatedIdentity");

  window.viamApi.setIdentity("");
  window.viamApi.setSessionData("", "");

  clearPinCodeTtl(authenticationPublicKey);

  localStorage.removeItem("uuid");
  localStorage.removeItem("token");
  localStorage.removeItem("authenticatedIdentity");

  window.currentlyAuthenticatedIdentity = null;
  window.lastTimeGetProfile = 0;
};

const destroyIdentity = () => {
  destroyAuthentication();

  if (window.currentlyLoadedIdentity) {
    const { publicKey } = window.currentlyLoadedIdentity.authentication;

    delete window.loadedIdentities[publicKey];
    window.currentlyLoadedIdentity = null;
    destroyIdentityFromLocalStorage(publicKey);
  }
};

window.loadedIdentities = {};
window.wopiAPI = new WopiAPI();
window.collaboraApi = new CollaboraAPI();
window.viamApi = new ViamAPI();
window.viamAnonymousApi = new ViamAPI();
window.currentlyAuthenticatedIdentity = null;
window.currentlyLoadedIdentity = null;
window.lastTimeGetProfile = 0;

let iframeParent = null;

const handleIdentityLogin = (identity, uuid, token) => {
  console.log({ identity });
  const { viamApi, loadedIdentities } = window;
  const { publicKey } = identity.authentication;
  console.log(loadedIdentities[publicKey]);
  viamApi.setSessionData(uuid, token);
  localStorage.setItem("uuid", uuid);
  localStorage.setItem("token", token);
  localStorage.setItem("authenticatedIdentity", publicKey);
  window.currentlyAuthenticatedIdentity = loadedIdentities[publicKey];
  window.lastTimeGetProfile = 0;
  setKeyForUUID(uuid, publicKey);
};

async function executeRestfulFunction(type, that, fn, config, ...args) {
  const {
    currentlyAuthenticatedIdentity,
    viamApi,
    currentlyLoadedIdentity
  } = window;
  let response;
  try {
    response = await fn.apply(that, [config, ...args]);
  } catch (error) {
    if (error.response) {
      //Resposnse with status code != 2xx still has valid response
      response = error.response;
    } else {
      //Connection error or similar
      const data = {
        data: "",
        code: "999",
        status: error.message
      };
      return data;
    }
  }

  const identity = currentlyAuthenticatedIdentity || currentlyLoadedIdentity;
  const { code, status } = response.data;

  const deviceRevoked =
    type === "private" && code === "401" && status === STATUS_DEVICE_REVOKED;
  if (deviceRevoked) {
    destroyIdentity();

    const event = createEvent("", "DeviceRevoked");
    iframeParent.onEvent(event);

    return response.data;
  }

  const userNotActivated =
    type === "private" &&
    code === "400" &&
    (status === STATUS_USER_NOT_ACTIVATED || status === STATUS_USER_BLOCKED);

  if (userNotActivated) {
    destroyIdentity();

    const event = createEvent("", "UserBlocked");
    iframeParent.onEvent(event);

    return response.data;
  }

  const badSession =
    type === "private" &&
    identity &&
    code === "400" &&
    status === "Bad session";
  if (!badSession) {
    return response.data;
  }

  const loginResponse = await viamApi.identityLogin(
    null,
    "previousaddeddevice"
  );

  if (loginResponse.data.code !== "200") {
    return loginResponse.data;
  }

  const uuid = loginResponse.data.data["Uuid"];
  const token = loginResponse.data.data["Session"];
  handleIdentityLogin(identity, uuid, token);
  try {
    response = await fn.apply(that, [config, ...args]);
  } catch (error) {
    response = error.response;
  }
  return response.data;
}

window.executeRestfulFunction = executeRestfulFunction;

const TRANSPARENT_PIXEL = new ImageData({
  contentType: "image/png",
  contentBase64:
    "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=" //1x1px transparent pixel
});

function loadIdentityInternal(identityKey, pinCode) {
  return new Penpal.Promise(result => {
    getIdentityFromLocalStorage(identityKey, pinCode)
      .then(async loadedIdentity => {
        if (loadedIdentity == null) {
          result({
            data: "",
            code: "400",
            status:
              "Please restore or authorize your account via another device."
          });
        }
        localStorage.removeItem("attempt");

        window.loadedIdentities[identityKey] = loadedIdentity;
        window.currentlyLoadedIdentity = loadedIdentity;

        if (identityKey === localStorage.getItem("authenticatedIdentity")) {
          window.currentlyAuthenticatedIdentity = loadedIdentity;
          const uuid = localStorage.getItem("uuid");
          const token = localStorage.getItem("token");
          const deviceHash = await createDeviceHash(identityKey);
          window.viamApi.setIdentity(identityKey);
          window.viamApi.setDeviceHash(deviceHash);
          window.viamApi.setSessionData(uuid, token);
        }

        window.viamAnonymousApi.setIdentity(
          window.currentlyLoadedIdentity.authentication.publicKey
        );

        const { publicKey, x509Certificate } = loadedIdentity.authentication;

        result({
          data: {
            authentication: {
              publicKey,
              x509Certificate
            }
          },
          code: "200",
          status: "Identity loaded"
        });
      })
      .catch(e => {
        result({
          data: "",
          code: "400",
          status: "" + e
        });
      });
  });
}

function getCertificateForPassport(passportUUID, internal) {
  return new Penpal.Promise(certificateResult => {
    if (window.currentlyAuthenticatedIdentity === null) {
      return { data: "", code: "400", status: "Identity not authenticated" };
    }

    const passportIdentity = window.currentlyAuthenticatedIdentity;
    const passport = passportIdentity.getPassport(passportUUID);
    if (passport === undefined || passport === null) {
      createPassportCertificate(passportUUID).then(function (keys) {
        const cryptoData = new CryptoData();
        cryptoData.setPublicKey(keys["publicKeyPEM"]);
        cryptoData.setPrivateKey(keys["privateKeyPEM"]);
        const certificate = keys["certificatePEM"];
        //download("passportCertificateBeforeSigning.crt", "text/plain", certificate)
        //cryptoData.setx509Certificate(keys["certificate"])
        executeRestfulFunction(
          "private",
          viamApi,
          viamApi.signSignCertificate,
          null,
          btoa(certificate),
          passportUUID
        ).then(executeResult => {
          if (executeResult.code === "200") {
            const signedCertificate = atob(
              executeResult.data["SignedCertificate"]
            );
            //download("passportCertificateAfterSigning.crt", "text/plain", signedCertificate)
            const keyUUID = executeResult.data["CertificateUUID"];
            const encodedChain = executeResult.data["Chain"];
            //download("rootCertificate.crt", "text/plain", atob(encodedChain[0]))

            const chain = [];

            for (let i = 0; i < encodedChain.length; i++) {
              chain.push(atob(encodedChain[i]));
            }

            cryptoData.setx509Certificate(signedCertificate);
            cryptoData.setKeyUUID(keyUUID);
            cryptoData.setChain(chain);

            passportIdentity.setPassport(passportUUID, cryptoData);

            getProfileData(passportIdentity).then(executeResult1 => {
              setIdentityInLocalStorage(passportIdentity)
                .then(() => {
                  window.currentlyAuthenticatedIdentity = passportIdentity;
                  window.lastTimeGetProfile = 0;
                  window.currentlyLoadedIdentity = passportIdentity;
                  const copyOfCryptoData = JSON.parse(
                    JSON.stringify(cryptoData)
                  );
                  if (internal === false) {
                    copyOfCryptoData["privateKey"] = "";
                  }

                  certificateResult({
                    data: copyOfCryptoData,
                    code: "200",
                    status: "Certificate got"
                  });
                })
                .catch(e => {
                  certificateResult({
                    data: "",
                    code: "400",
                    status: "Can not store certificate " + e
                  });
                });
            });
          } else {
            certificateResult(executeResult);
          }
        });
      });
    } else {
      const copyOfCryptoData = JSON.parse(JSON.stringify(passport));

      if (internal === false) {
        copyOfCryptoData["privateKey"] = "";
      }

      certificateResult({
        data: copyOfCryptoData,
        code: "200",
        status: "Certificate got"
      });
    }
  });
}

const connection = Penpal.connectToParent({
  // Methods child is exposing to parent
  methods: {
    initialize: (apiUrl, wopiUrl, collaboraUrl) => {
      if (!apiUrl) {
        apiUrl = `${window.location.origin}/api/`;
        console.warn(`API host URL not specified. Fall back to ${apiUrl}`); // eslint-disable-line no-console
      }

      if (!wopiUrl) {
        wopiUrl = `${window.location.origin}/wopi/`;
        console.warn(`WOPI host URL not specified. Fall back to ${wopiUrl}`); // eslint-disable-line no-console
      }

      if (!collaboraUrl) {
        collaboraUrl = window.location.origin;
        console.warn(
          `Collabora host URL not specified. Fall back to ${collaboraUrl}`
        ); // eslint-disable-line no-console
      }

      window.API_HOST =
        apiUrl.charAt(apiUrl.length - 1) === "/" ? apiUrl : apiUrl + "/";
      window.WOPI_URL =
        wopiUrl.charAt(wopiUrl.length - 1) === "/" ? wopiUrl : wopiUrl + "/";
      window.COLLABORA_URL =
        collaboraUrl.charAt(collaboraUrl.length - 1) === "/"
          ? collaboraUrl
          : collaboraUrl + "/";
    },
    ...penpalMethods,
    createIdentity(pinCode) {
      return new Penpal.Promise(result => {
        createPassportCertificate(makeid()).then(function (keys) {
          const newIdentity = new Identity();
          const cryptoData = new CryptoData();
          cryptoData.setPublicKey(keys["publicKeyPEM"]);
          cryptoData.setPrivateKey(keys["privateKeyPEM"]);
          cryptoData.setx509Certificate(keys["certificatePEM"]);
          newIdentity.setAuthentication(cryptoData);
          newIdentity.setPinCode(pinCode);

          window.currentlyLoadedIdentity = newIdentity;
          localStorage.setItem(
            "currentlyLoadedIdentity",
            JSON.stringify(newIdentity)
          );
          const { publicKey, x509Certificate } = newIdentity.authentication;

          window.loadedIdentities[publicKey] = newIdentity;
          extendPinCodeTtl(newIdentity.authentication.publicKey, pinCode);

          window.viamAnonymousApi.setIdentity(
            newIdentity.authentication.publicKey
          );

          result({
            data: {
              authentication: {
                publicKey,
                x509Certificate
              }
            },
            code: "200",
            status: "Identity created"
          });
        });
      });
    },
    listIdentities() {
      return new Penpal.Promise(result => {
        const identities = listIdentitiesFromLocalStorage();
        result({ data: identities, code: "200", status: "Identities listed" });
      });
    },
    loadIdentity(identityKey, pinCode) {
      return loadIdentityInternal(identityKey, pinCode);
    },
    checkIdentityPinCode: async (key, pinCode) => {
      try {
        const identity = await getIdentityFromLocalStorage(key, pinCode, false);

        if (identity) {
          return encodeResponse("200", null, "Pincode check successful");
        } else {
          return encodeResponse("400", null, "Pincode check failed");
        }
      } catch (e) {
        return encodeResponse("400", e, "Pincode check error");
      }
    },
    changeIdentityPinCode: async (key, oldPinCode, newPinCode) => {
      try {
        const identity = await getIdentityFromLocalStorage(
          key,
          oldPinCode,
          false
        );

        if (identity) {
          identity.pinCode = newPinCode;
          await setIdentityInLocalStorage(identity);
          window.currentlyAuthenticatedIdentity = identity;
          window.currentlyLoadedIdentity = identity;

          return encodeResponse("200", null, "Successfully changed pincode");
        } else {
          return encodeResponse("400", null, "Identity not found");
        }
      } catch (e) {
        return encodeResponse("400", e.message, "Change pincode error");
      }
    },
    getIdentityProfile(identityKey) {
      return new Penpal.Promise(result => {
        const serializedProfile = localStorage.getItem(
          "profiles/" + identityKey
        );
        if (serializedProfile === null || serializedProfile === "") {
          result({ data: "", code: "400", status: "Profile is empty" });
        } else {
          result({
            data: JSON.parse(serializedProfile),
            code: "200",
            status: "Identities cleared"
          });
        }
      });
    },
    clearIdentities: async () => {
      destroyAuthentication();

      const identitiesTemp = listIdentitiesFromLocalStorage();

      for (const i in identitiesTemp) {
        destroyIdentityFromLocalStorage(i);
      }
      return encodeResponse("200", "", "Identities cleared");
    },
    confirmIdentificator(identity, confirmationCodeArg) {
      return new Penpal.Promise(result => {
        viamApi.setIdentity(identity.authentication.publicKey);

        executeRestfulFunction(
          "public",
          viamApi,
          viamApi.identityConfirmIdentificator,
          null,
          confirmationCodeArg
        ).then(executeResult => {
          result(executeResult);
        });
      });
    },
    identityGetIdentificatorByRegisterToken(identity, tokenArg) {
      return new Penpal.Promise(result => {
        viamApi.setIdentity(identity.authentication.publicKey);

        executeRestfulFunction(
          "public",
          viamApi,
          viamApi.identityGetIdentificatorByRegisterToken,
          null,
          tokenArg
        ).then(executeResult => {
          result(executeResult);
        });
      });
    },
    submitIdentificator(identity, identificatorArg, registerToken) {
      return new Penpal.Promise(result => {
        viamApi.setIdentity(identity.authentication.publicKey);

        executeRestfulFunction(
          "public",
          viamApi,
          viamApi.identitySubmitIdentificator,
          null,
          identificatorArg,
          registerToken
        ).then(executeResult => {
          result(executeResult);
        });
      });
    },
    submitRegisterClaims(
      identity,
      givennameArg,
      familynameArg,
      emailArg,
      phonenumberArg
    ) {
      return new Penpal.Promise(result => {
        viamApi.setIdentity(identity.authentication.publicKey);

        executeRestfulFunction(
          "public",
          viamApi,
          viamApi.identitySubmitRegisterClaims,
          null,
          givennameArg,
          familynameArg,
          emailArg,
          phonenumberArg
        ).then(executeResult => {
          result(executeResult);
        });
      });
    },
    finalizeEmployeeRegistration: async (identity, identifier) => {
      viamApi.setIdentity(identity.authentication.publicKey);
      return executeRestfulFunction(
        "public",
        viamApi,
        viamApi.identityFinalizeEmployeeRegistration,
        null,
        identifier
      );
    },
    agreeOnRegistration(registerIdentity) {
      return new Penpal.Promise(result => {
        viamApi.setIdentity(registerIdentity.authentication.publicKey);

        executeRestfulFunction(
          "public",
          viamApi,
          viamApi.identityAgreeOnRegistration,
          null
        ).then(executeResult => {
          let sequence = Promise.resolve();
          if (executeResult.code === "200") {
            sequence = sequence.then(() => {
              setIdentityInLocalStorage(registerIdentity);
            });
          }
          sequence
            .then(() => {
              result(executeResult);
            })
            .catch(e => {
              result({
                data: "",
                code: "400",
                status: "Can not store identity: " + e
              });
            });
        });
      });
    },
    resendConfirmationCode(identity, identificatorArg) {
      return new Penpal.Promise(result => {
        viamApi.setIdentity(identity.authentication.publicKey);

        executeRestfulFunction(
          "public",
          viamApi,
          viamApi.identityResendConfirmationCode,
          null,
          identificatorArg
        ).then(executeResult => {
          result(executeResult);
        });
      });
    },
    login: async (loginIdentity, mode, requestCode, requestActionID) => {
      if (!window.loadedIdentities[loginIdentity.authentication.publicKey]) {
        return {
          data: "",
          code: "400",
          status: "Identity not loaded"
        };
      }

      const deviceHash = await createDeviceHash(
        loginIdentity.authentication.publicKey
      );
      window.viamApi.setSessionData("", "");
      window.viamApi.setDeviceHash(deviceHash);
      window.viamApi.setIdentity(loginIdentity.authentication.publicKey);

      const identityLoginResponse = await executeRestfulFunction(
        "public",
        window.viamApi,
        window.viamApi.identityLogin,
        null,
        mode,
        requestCode,
        requestActionID
      );

      const { code, data } = identityLoginResponse;
      const responseToClient = Object.assign({}, identityLoginResponse);

      if (code === "200") {
        if (mode === LOGIN_MODES.PREVIOUSLY_ADDED_DEVICE) {
          handleIdentityLogin(loginIdentity, data.Uuid, data.Session);
          await getProfileData(loginIdentity);
        } else if (mode === LOGIN_MODES.NEW_DEVICE) {
          const dataUrl = await generateQrCode(
            `${data.ActionID},${data.QrCode}`
          );
          Object.assign(responseToClient.data, { image: dataUrl });
        }
      }

      return responseToClient;
    },
    identityAddNewDevice() {
      return new Penpal.Promise(result => {
        const authenticationPublicKey = localStorage.getItem(
          "authenticatedIdentity"
        );

        if (authenticationPublicKey === null) {
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
          });
        }

        if (window.loadedIdentities[authenticationPublicKey] === null) {
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
          });
        }

        const success = extendPinCodeTtl(authenticationPublicKey);

        if (success === false) {
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
          });
        }

        executeRestfulFunction(
          "private",
          viamApi,
          viamApi.identityAddNewDevice,
          null
        ).then(async executeResult => {
          if (executeResult.code === "200") {
            const actionID = executeResult.data["ActionID"];
            const QrCode = executeResult.data["QrCode"];
            const dataUrl = await generateQrCode(actionID + "," + QrCode);
            executeResult.data["image"] = dataUrl;
            result(executeResult);
          } else {
            result(executeResult);
          }
        });
      });
    },
    identityDestroyKeysForDevice(authenticationPublicKeyArg) {
      return new Penpal.Promise(result => {
        const authenticationPublicKey = localStorage.getItem(
          "authenticatedIdentity"
        );
        if (authenticationPublicKey === null) {
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
          });
        }
        if (window.loadedIdentities[authenticationPublicKey] === null) {
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
          });
        }

        const success = extendPinCodeTtl(authenticationPublicKey);

        if (success === false) {
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
          });
        }

        executeRestfulFunction(
          "private",
          viamApi,
          viamApi.identityDestroyKeysForDevice,
          null,
          btoa(authenticationPublicKeyArg)
        ).then(executeResult => {
          result(executeResult);
        });
      });
    },
    logout: async () => {
      try {
        const authenticationPublicKey = localStorage.getItem(
          "authenticatedIdentity"
        );
        if (
          !authenticationPublicKey ||
          !window.loadedIdentities[authenticationPublicKey]
        ) {
          return {
            data: "",
            code: "400",
            status: "Identity not loaded"
          };
        }

        // Clone headers to be able destroy authentication first.
        // We need it because clients should be able reload page right after logout invocation and not wait until request completed
        const headers = { ...window.viamApi.getConfig().headers };

        destroyAuthentication();

        return executeRestfulFunction(
          "private",
          window.viamApi,
          window.viamApi.identityLogout,
          {
            headers
          }
        );
      } catch (e) {
        return {
          data: "",
          code: "400",
          status: e.message
        };
      }
    },
    identityRestoreAccess(restoreAccessIdentity, identificator, restoreType) {
      return new Penpal.Promise(result => {
        viamApi.setSessionData("", "");
        viamApi.setIdentity(restoreAccessIdentity.authentication.publicKey);

        executeRestfulFunction(
          "public",
          viamApi,
          viamApi.identityRestoreAccess,
          null,
          identificator,
          restoreType
        ).then(executeResult => {
          result(executeResult);
        });
      });
    },
    identityInitiateSocialRecovery: async accessToken => {
      const response = await executeRestfulFunction(
        "public",
        viamApi,
        viamApi.identityInitiateSocialRecovery,
        null,
        accessToken
      );

      return response;
    },
    contactsCheckAccountRecoveryStatus: async () => {
      const currentlyLoadedIdentity = localStorage.getItem(
        "currentlyLoadedIdentity"
      );
      const parsedIdentity = JSON.parse(currentlyLoadedIdentity);
      window.currentlyLoadedIdentity = parsedIdentity;
      const { publicKey } = parsedIdentity.authentication;
      window.loadedIdentities[publicKey] = parsedIdentity;
      window.viamAnonymousApi.setIdentity(publicKey);
      window.viamApi.setSessionData("", "");
      window.viamApi.setIdentity(publicKey);

      const timeout = ms => new Promise(resolve => setTimeout(resolve, ms));

      async function checkAccountRecoveryStatus() {
        const response = await executeRestfulFunction(
          "public",
          viamApi,
          viamApi.contactsCheckAccountRecoveryStatus,
          null
        );

        if (response.data === 0) {
          await timeout(1000);
          await checkAccountRecoveryStatus();
          return;
        }

        const deviceHash = await createDeviceHash(publicKey);
        window.viamApi.setDeviceHash(deviceHash);

        const identityLoginResponse = await executeRestfulFunction(
          "public",
          window.viamApi,
          window.viamApi.identityLogin,
          null,
          "previousaddeddevice"
        );

        const { code, data } = identityLoginResponse;

        if (code === "200") {
          await setIdentityInLocalStorage(parsedIdentity);
          handleIdentityLogin(parsedIdentity, data.Uuid, data.Session);
          await getProfileData(parsedIdentity);
          localStorage.removeItem("currentlyLoadedIdentity");
        }
      }

      await checkAccountRecoveryStatus();
    },
    contactsGetTrusteeContactsPublicKeys: async () => {
      try {
        const response = await executeRestfulFunction(
          "private",
          window.viamApi,
          window.viamApi.contactsGetTrusteeContactsPublicKeys,
          null
        );

        if (response.code !== "200") {
          return response;
        }

        const responseData = response.data;
        const trusteesDevices = Object.values(responseData);

        /** Check if there are new trustees without added secret part */
        const hasNewTrustees = trusteesDevices.some(device => {
          const deviceData = Object.values(device);
          return deviceData.some(data => data.hasShamir === "0");
        });

        if (!hasNewTrustees) {
          return response;
        }

        // Generate and split recovery key
        const trusteesUuids = Object.keys(responseData);
        const trusteesToDevices = Object.entries(responseData);
        const sharesNumber = trusteesUuids.length;
        const recoveryKey = generateRecoveryKey();
        let recoveryKeyShares = [recoveryKey];
        // Split the secret when sharesNumber is more than 1 because VereignPublicKey is always returned
        if (sharesNumber > 1) {
          recoveryKeyShares = getRecoveryKeyShares(recoveryKey, sharesNumber);
          const sanityCheckResponse = checkRecoveryKeyCombine(
            recoveryKey,
            recoveryKeyShares
          );

          if (sanityCheckResponse.code !== "200") {
            return sanityCheckResponse;
          }
        }

        // Encrypt each share with every publicKey of each contact device
        const shamirPartsList = await Promise.all(
          trusteesToDevices.map(async ([contactUuid, device], index) => {
            const deviceIdsToPublicKeys = Object.entries(device);
            // Encrypt secret shares in parallel
            const deviceIdsToEncryptedPartsList = await Promise.all(
              deviceIdsToPublicKeys.map(async ([deviceId, { content }]) => {
                const encryptedShare = await encryptShare(
                  recoveryKeyShares[index],
                  content
                );

                return [deviceId, encryptedShare];
              })
            );
            // Turn deviceIdsToEncryptedPartsList array to object
            const deviceIdsToEncryptedParts = Object.fromEntries(
              deviceIdsToEncryptedPartsList
            );

            return [contactUuid, deviceIdsToEncryptedParts];
          })
        );
        // Turn shamirPartsList array to object
        const shamirParts = Object.fromEntries(shamirPartsList);

        // Save Shamir parts to database
        const saveShamirPartsResponse = await executeRestfulFunction(
          "private",
          window.viamApi,
          window.viamApi.contactsSaveShamirParts,
          null,
          shamirParts
        );

        if (saveShamirPartsResponse.code !== "200") {
          return saveShamirPartsResponse;
        }

        return response;
      } catch (error) {
        return encodeResponse("400", "", error.message);
      }
    },
    parseSMIME,
    getCurrentlyLoggedInUUID() {
      return new Penpal.Promise(result => {
        const authenticationPublicKey = localStorage.getItem(
          "authenticatedIdentity"
        );
        if (authenticationPublicKey === null) {
          return { data: "", code: "400", status: "Identity not loaded" };
        }
        if (window.loadedIdentities[authenticationPublicKey] === null) {
          return { data: "", code: "400", status: "Identity not loaded" };
        }

        const success = extendPinCodeTtl(authenticationPublicKey);

        if (success === false) {
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
          });
        }

        if (localStorage.getItem("uuid") === null) {
          result({ data: "", code: "400", status: "Not logged in UUID" });
        }
        result({
          data: localStorage.getItem("uuid"),
          code: "200",
          status: "UUID loaded"
        });
      });
    },
    getCertificateByPassport(passportUUID) {
      return new Penpal.Promise(result => {
        const authenticationPublicKey = localStorage.getItem(
          "authenticatedIdentity"
        );
        if (authenticationPublicKey === null) {
          return { data: "", code: "400", status: "Identity not loaded" };
        }
        if (window.loadedIdentities[authenticationPublicKey] === null) {
          return { data: "", code: "400", status: "Identity not loaded" };
        }

        const success = extendPinCodeTtl(authenticationPublicKey);

        if (success === false) {
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
          });
        }

        getCertificateForPassport(passportUUID, false).then(
          certificateResult => {
            result(certificateResult);
          }
        );
      });
    },
    getOneTimeCertificateByPassport(passportUUID, emailArg) {
      return new Penpal.Promise(result => {
        const authenticationPublicKey = localStorage.getItem(
          "authenticatedIdentity"
        );
        if (authenticationPublicKey === null) {
          return { data: "", code: "400", status: "Identity not loaded" };
        }
        if (window.loadedIdentities[authenticationPublicKey] === null) {
          return { data: "", code: "400", status: "Identity not loaded" };
        }

        const success = extendPinCodeTtl(authenticationPublicKey);

        if (success === false) {
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
          });
        }

        getCertificateForPassport(passportUUID, true).then(
          certificateResult => {
            if (certificateResult.code === "200") {
              const passportCertificate =
                certificateResult.data["x509Certificate"];
              const passportPrivateKey = certificateResult.data["privateKey"];
              const passportChain = certificateResult.data["chain"];

              createOneTimePassportCertificate(
                makeid() + "-" + passportUUID,
                emailArg,
                passportPrivateKey,
                passportCertificate
              ).then(function (keys) {
                const publicKeyOneTime = keys["publicKeyPEM"];
                const privateKeyOneTime = keys["privateKeyPEM"];
                const certificateOneTime = keys["certificatePEM"];
                passportChain.push(passportCertificate);

                const oneTimeCryptoData = new CryptoData();
                oneTimeCryptoData.setx509Certificate(certificateOneTime);
                oneTimeCryptoData.setPrivateKey(privateKeyOneTime);
                oneTimeCryptoData.setPublicKey(publicKeyOneTime);
                oneTimeCryptoData.setChain(passportChain);

                result({
                  data: oneTimeCryptoData,
                  code: "200",
                  status: "One time certificate generated"
                });
                // Prints PEM formatted signed certificate
                // -----BEGIN CERTIFICATE-----MIID....7Hyg==-----END CERTIFICATE-----
              });
            } else {
              result({
                data: "",
                code: "400",
                status: "Can not generate one time certificate"
              });
            }
          }
        );
      });
    },
    verifySMIME: async smimeString => {
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );

      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      //TODO cache (for some time) the root certificate
      // either as PEM or as certificate object (preferred)
      const rootCaResponse = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.signRetrieveRootCertificate,
        null
      );

      if (rootCaResponse.code !== "200") {
        return encodeResponse("400", "", rootCaResponse.status);
      }

      const rootCaPem = rootCaResponse.data;
      const verificationResult = await verifySMIME(smimeString, rootCaPem);

      return encodeResponse(
        "200",
        verificationResult.verified,
        verificationResult.message
      );
    },
    validateDocument: async (documentUUID, contentType) => {
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );

      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      const validateDocumentResponse = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.documentValidateDocumentByUUID,
        null,
        documentUUID,
        contentType
      );

      if (validateDocumentResponse.code !== "200") {
        return encodeResponse("400", "", validateDocumentResponse.status);
      }

      const signatures = validateDocumentResponse.data;
      if (signatures) {
        for (const signature of signatures) {
          const certificateChain = signature.certificateChainPEM.map(
            certificatePEM => {
              const certificate = parseCertificate(certificatePEM);
              const certificateData = new CertificateData(certificate);
              return certificateData;
            }
          );
          signature.certificateChain = certificateChain;
        }
      }

      return validateDocumentResponse;
    },
    signEmail: async (passportUUID, emailArg, emailMessage) => {
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );

      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      let response = await getCertificateForPassport(passportUUID, true);

      if (response.code !== "200") {
        return encodeResponse("400", "", response.status);
      }

      const {
        x509Certificate: passportCertificate,
        privateKey: passportPrivateKey,
        chain: passportChain
      } = response.data;

      const keys = await createOneTimePassportCertificate(
        makeid() + "-" + passportUUID,
        emailArg,
        passportPrivateKey,
        passportCertificate
      );

      const {
        privateKeyPEM: privateKeyOneTime,
        certificatePEM: certificateOneTime
      } = keys;

      passportChain.push(passportCertificate);

      response = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.passportGetEmailWithHeaderByPassport,
        null,
        passportUUID,
        emailMessage
      );

      if (response.code !== "200") {
        return encodeResponse("400", "", response.status);
      }

      const signedEmail = await signEmail(
        response.data,
        certificateOneTime,
        passportChain,
        privateKeyOneTime
      );

      response = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.signResignEmail,
        null,
        passportUUID,
        signedEmail
      );

      if (response.code !== "200") {
        return encodeResponse("400", "", response.status);
      }

      return encodeResponse("200", response.data, "Email signed");
    },
    signDocument: async (passportUUID, documentUUID, documentContentType) => {
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );

      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      const certResponse = await getCertificateForPassport(passportUUID, true);

      if (certResponse.code !== "200") {
        return encodeResponse("400", "", certResponse.status);
      }

      const {
        x509Certificate: passportCertificate,
        privateKey: passportPrivateKey,
        chain: passportChain
      } = certResponse.data;

      const keys = await createOneTimePassportCertificate(
        makeid() + "-" + passportUUID,
        null,
        passportPrivateKey,
        passportCertificate
      );

      const {
        privateKeyPEM: privateKeyOneTime,
        certificatePEM: certificateOneTime
      } = keys;

      passportChain.reverse();

      passportChain.push(passportCertificate);

      passportChain.reverse();

      const pdfContentType = "application/pdf";

      if (documentContentType !== pdfContentType) {
        const convResponse = await executeRestfulFunction(
          "private",
          window.viamApi,
          window.viamApi.documentConvertDocumentByUUID,
          null,
          documentUUID,
          documentContentType,
          pdfContentType
        );
        if (convResponse.code !== "200") {
          return encodeResponse("400", "", convResponse.status);
        }
      }

      const downloadResponse = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.documentGetDocumentByUUID,
        null,
        documentUUID,
        pdfContentType
      );

      if (downloadResponse.code !== "200") {
        return encodeResponse("400", "", downloadResponse.status);
      }

      const pdfRaw = base64ToByteArray(downloadResponse.data);

      let signedPdf;
      try {
        signedPdf = await signPdf(
          pdfRaw,
          certificateOneTime,
          passportChain,
          privateKeyOneTime
        );
      } catch (err) {
        console.error(err);
        return encodeResponse("500", "", err.message);
      }

      const signedPdfB64 = byteArrayToBase64(signedPdf);

      const uploadResponse = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.documentPutDocumentByUUID,
        null,
        documentUUID,
        pdfContentType,
        signedPdfB64
      );
      if (uploadResponse.code !== "200") {
        return encodeResponse("400", "", uploadResponse.status);
      }

      const signResponse = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.documentSignDocumentByUUID,
        null,
        passportUUID,
        documentUUID,
        pdfContentType
      );
      if (signResponse.code !== "200") {
        return encodeResponse("400", "", signResponse.status);
      }

      return encodeResponse("200", "", "Document signed");
    },
    /**
     * Checks the state of the vCard. Current states are "disabled" and "enabled".
     * @param passportUUID
     * @returns {Promise<{code: *, data: *, status: *}>}
     */
    async checkVCardState(passportUUID) {
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );
      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      let vCardImageClaimValue;

      const vCardImageClaimName = "vCardImage";
      const defaultTagName = "notag";

      const vCardClaimResponse = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.entityGetClaim,
        null,
        vCardImageClaimName,
        defaultTagName,
        passportUUID
      );

      // You may not have vCard image claim, but backend will still return blank image from passportGetVCardImage

      // if (vCardClaimResponse.code !== "200") {
      //   return encodeResponse("400", "", vCardClaimResponse.status);
      // }

      if (vCardClaimResponse.code === "200") {
        vCardImageClaimValue = vCardClaimResponse.data;
      }

      if (vCardImageClaimValue && "state" in vCardImageClaimValue) {
        return encodeResponse("200", vCardImageClaimValue.state, "OK");
      }

      return encodeResponse("200", "enabled", "OK");
    },
    /**
     * 
     * @param passportUUID
     * @param documentUUID
     * @param documentContentType
     * @param signatureData -
     * @returns {Promise<{code: *, data: *, status: *}>}
     *
// Image position in pixels
message Position {
  int32 left = 1; // x
  int32 top = 2; // y
}

     // Image size in pixels
     message Size {
  uint32 width = 1;
  uint32 height = 2;
}

     // Image size in pixels
     message Bounds {
  int32 left = 1; // x
  int32 top = 2; // y
  uint32 width = 3;
  uint32 height = 4;
}
message SignatureData {
  ImageData signatureImageData = 1;
  Bounds signatureBounds = 2;
  uint32 pageNumber = 3;
  Size pageSize = 4;
}
     * 
     */
    signDocumentJava: async (
      passportUUID,
      documentUUID,
      documentContentType,
      signatureData = null,
      qrCodeUrl = null
    ) => {
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );

      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      // Get vCard and QR Code Coordinates

      let vCardImageData;
      let vCardImageClaimValue;

      let qrCodeImageData;
      let qrCodeCoordinates = { fromL: -1, fromR: -1, toL: -1, toR: -1 };

      if (signatureData) {
        const vCardImageClaimName = "vCardImage";
        const defaultTagName = "notag";

        const vCardClaimResponse = await executeRestfulFunction(
          "private",
          window.viamApi,
          window.viamApi.entityGetClaim,
          null,
          vCardImageClaimName,
          defaultTagName,
          passportUUID
        );
        // if (vCardClaimResponse.code !== "200") {
        //   return encodeResponse("400", "", vCardClaimResponse.status);
        // }

        if (vCardClaimResponse.code === "200") {
          vCardImageClaimValue = vCardClaimResponse.data;
        }

        if (
          vCardImageClaimValue &&
          "state" in vCardImageClaimValue &&
          vCardImageClaimValue.state === "disabled"
        ) {
          vCardImageData = TRANSPARENT_PIXEL;
        } else {
          const vCardImageResponse = await executeRestfulFunction(
            "private",
            window.viamApi,
            window.viamApi.passportGetVCardImage,
            null,
            passportUUID
          );

          if (vCardImageResponse.code !== "200") {
            return encodeResponse("400", "", vCardImageResponse.status);
          }
          vCardImageData = new ImageData(vCardImageResponse.data.Image);
          if (vCardImageData.contentType !== "image/png") {
            return encodeResponse(
              "400",
              "",
              "Content type of vCard must be 'image/png'"
            );
          }

          if (qrCodeUrl) {
            qrCodeCoordinates = vCardImageResponse.data.QRCodeCoordinates;
            const qrCodeBase64Content = await generateQrCode(qrCodeUrl);
            qrCodeImageData = new ImageData({
              contentType: "image/png",
              content: qrCodeBase64Content
            });
          }
        }

        // Fill signature data onject

        signatureData.signatureImageData = vCardImageData;
      }

      // Generate certificate chain

      const certResponse = await getCertificateForPassport(passportUUID, true);

      if (certResponse.code !== "200") {
        return encodeResponse("400", "", certResponse.status);
      }

      const {
        x509Certificate: passportCertificate,
        privateKey: passportPrivateKey,
        chain: passportChain
      } = certResponse.data;

      const keys = await createOneTimePassportCertificate(
        makeid() + "-" + passportUUID,
        null,
        passportPrivateKey,
        passportCertificate
      );

      const {
        privateKeyPEM: privateKeyOneTime,
        certificatePEM: certificateOneTime
      } = keys;

      passportChain.reverse();

      passportChain.push(passportCertificate);
      passportChain.push(certificateOneTime);

      passportChain.reverse();

      // eventually convert document

      const pdfContentType = "application/pdf";

      if (documentContentType !== pdfContentType) {
        const convResponse = await executeRestfulFunction(
          "private",
          window.viamApi,
          window.viamApi.documentConvertDocumentByUUID,
          null,
          documentUUID,
          documentContentType,
          pdfContentType
        );
        if (convResponse.code !== "200") {
          return encodeResponse("400", "", convResponse.status);
        }
      }

      // Sign document

      const signResponse = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.documentSignDocumentJavaService,
        null,
        privateKeyOneTime,
        passportChain,
        passportUUID,
        documentUUID,
        pdfContentType,
        signatureData,
        qrCodeImageData,
        qrCodeCoordinates
      );
      if (signResponse.code !== "200") {
        return encodeResponse("400", "", signResponse.status);
      }

      return encodeResponse("200", "", "Document signed");
    },
    // passportUUID - String - passport to sign the vCard
    // senderEmail - the email address of the sender
    // attribs - additional attributes, to be added to the vCard. Recognized attribs are:
    //   - emailService: "GMail", "Outlook 365", ...
    // text, html - String - the text and html part of the email, both optional
    // parts - array of objects, representing the MIME structure in format:
    // {
    //   headers: {
    //     "Content-Type": "image/jpeg",
    //     "Content-Disposition": "inline" or "attachment" with additional attributes,
    //     ... //other headers from MIME
    //   },
    //   body: String if it is a text part (Content-Type = "text/...") or Uint8Array otherwise; filled for leaf MIME nodes
    //   parts: array of instances of the same object; filled for container MIME nodes (Content-Type = "multipart/...")
    // }
    signVCard: async (
      passportUUID,
      senderEmail,
      attribs,
      textBody,
      htmlBody,
      parts
    ) => {
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );

      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      const messageUUID = makeid();

      const vCardAttribs = {
        ...attribs,
        passportUUID,
        messageUUID
      };

      let vCardImageData;
      let vCardImageClaimValue;

      let qrCodeImageData;
      let qrCodeCoordinates = { fromL: -1, fromR: -1, toL: -1, toR: -1 };

      const vCardImageClaimName = "vCardImage";
      const defaultTagName = "notag";

      const vCardClaimResponse = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.entityGetClaim,
        null,
        vCardImageClaimName,
        defaultTagName,
        passportUUID
      );
      // if (vCardClaimResponse.code !== "200") {
      //   return encodeResponse("400", "", vCardClaimResponse.status);
      // }

      if (vCardClaimResponse.code === "200") {
        vCardImageClaimValue = vCardClaimResponse.data;
      }

      if (
        vCardImageClaimValue &&
        "state" in vCardImageClaimValue &&
        vCardImageClaimValue.state === "disabled"
      ) {
        vCardImageData = TRANSPARENT_PIXEL;
      } else {
        const vCardImageResponse = await executeRestfulFunction(
          "private",
          window.viamApi,
          window.viamApi.passportGetVCardImage,
          null,
          passportUUID
        );

        if (vCardImageResponse.code !== "200") {
          return encodeResponse("400", "", vCardImageResponse.status);
        }
        vCardImageData = new ImageData(vCardImageResponse.data.Image);
        if (vCardImageData.contentType !== "image/png") {
          return encodeResponse(
            "400",
            "",
            "Content type of vCard must be 'image/png'"
          );
        }

        qrCodeCoordinates = vCardImageResponse.data.QRCodeCoordinates;
        const qrCodeBase64Content = await generateQrCode(
          "https://" + location.host + "/check/" + messageUUID
        );
        qrCodeImageData = new ImageData({
          contentType: "image/png",
          content: qrCodeBase64Content
        });
      }

      if (typeof parts === "undefined" || parts === null) {
        parts = [];
      }

      if (htmlBody) {
        if (typeof htmlBody !== "string") {
          throw new Error("htmlBody is not string"); // should not happen
        }
        const htmlPart = {
          headers: {
            "Content-Type": "text/html"
          },
          body: htmlBody
        };
        parts.unshift(htmlPart);
      } else {
        console.log("Html body is not passed to signVCard, its value is ", {
          html: htmlBody
        });
      }

      if (textBody) {
        if (typeof textBody !== "string") {
          throw new Error("textBody is not string"); // should not happen
        }
        const textPart = {
          headers: {
            "Content-Type": "text/plain"
          },
          body: textBody
        };
        parts.unshift(textPart);
      } else {
        console.log("Text body is not passed to signVCard, its value is ", {
          text: textBody
        });
      }

      const count = await prepareVCardParts(parts);
      if (count.textParts === 0) {
        return encodeResponse("400", "", "No text parts passed to signVCard");
      }
      if (count.htmlParts === 0) {
        return encodeResponse("400", "", "No html parts passed to signVCard");
      }

      const certResponse = await getCertificateForPassport(passportUUID, true);

      if (certResponse.code !== "200") {
        return encodeResponse("400", "", certResponse.status);
      }

      const {
        x509Certificate: passportCertificate,
        privateKey: passportPrivateKey,
        chain: passportChain
      } = certResponse.data;

      const keys = await createOneTimePassportCertificate(
        makeid() + "-" + passportUUID,
        senderEmail,
        passportPrivateKey,
        passportCertificate
      );

      const {
        privateKeyPEM: privateKeyOneTime,
        certificatePEM: certificateOneTime
      } = keys;

      passportChain.reverse();

      passportChain.push(passportCertificate);
      passportChain.push(certificateOneTime);

      passportChain.reverse();

      const signVCardResponse = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.signSignVCardForChain,
        null,
        vCardImageData,
        privateKeyOneTime,
        passportChain,
        parts,
        vCardAttribs,
        qrCodeImageData,
        qrCodeCoordinates
      );
      if (signVCardResponse.code !== "200") {
        return encodeResponse("400", "", signVCardResponse.status);
      }

      const signedVCardImageData = new ImageData(signVCardResponse.data);
      return encodeResponse(
        "200",
        {
          image: signedVCardImageData,
          messageUUID: messageUUID
        },
        "vCard signed"
      );
    },
    // mime - String - the MIME of the email message
    // vCardAttribs - optional attributes for the verification procedure in format
    // {
    //   passportUUID: passportUUID,
    //   messageUUID: messageUUID
    // };
    validateVMime: async (vMime, vCardAttribs = null) => {
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );

      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      const validateVMimeResponse = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.signValidateVMime,
        null,
        vMime,
        vCardAttribs
      );
      if (validateVMimeResponse.code !== "200") {
        return encodeResponse("400", "", validateVMimeResponse.status);
      }

      const validationResult = validateVMimeResponse.data;
      const { signatures } = validationResult;
      if (signatures) {
        for (const signature of signatures) {
          const certificateChain = signature.certificateChainPEM.map(
            certificatePEM => {
              const certificate = parseCertificate(certificatePEM);
              const certificateData = new CertificateData(certificate);
              return certificateData;
            }
          );
          signature.certificateChain = certificateChain;
        }
      }

      return encodeResponse(
        "200",
        validationResult,
        "Validation result retrieved"
      );
    },
    generateQrCode,
    documentCreateDocument: async (passportUUID, path, contentType, title) => {
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );
      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      path = encodeURI(path);
      contentType = encodeURI(contentType);
      title = encodeURI(title);

      const config = {
        headers: {
          path,
          passportuuid: passportUUID,
          contentType,
          title
        }
      };
      const response = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.documentCreateDocument,
        config
      );

      if (response.code !== "200") {
        return encodeResponse("400", "", response.status);
      }

      return encodeResponse("200", response.data, "Document created");
    },
    getVcardWithQrCode: async (passportUUID, QRCodeContent = null) => {
      //TODO: IMPLEMENT QR CODE backend method needed
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );

      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      let vCardImageData;
      let vCardImageClaimValue;

      const vCardImageClaimName = "vCardImage";
      const defaultTagName = "notag";

      const vCardClaimResponse = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.entityGetClaim,
        null,
        vCardImageClaimName,
        defaultTagName,
        passportUUID
      );

      if (vCardClaimResponse.code === "200") {
        vCardImageClaimValue = vCardClaimResponse.data;
      }

      if (
        vCardImageClaimValue &&
        "state" in vCardImageClaimValue &&
        vCardImageClaimValue.state === "disabled"
      ) {
        //No image data if the user have disabled vCard for this profile.
        vCardImageData = null;

        // vCardImageData = new ImageData({
        //   contentType: "image/png",
        //   contentBase64:
        //     "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=" //1x1px transparent pixel
        // });
      } else {
        const vCardImageResponse = await executeRestfulFunction(
          "private",
          window.viamApi,
          window.viamApi.passportGetVCardImage,
          null,
          passportUUID
        );

        if (vCardImageResponse.code !== "200") {
          return encodeResponse("400", "", vCardImageResponse.status);
        }
        vCardImageData = new ImageData(vCardImageResponse.data.Image);
        if (vCardImageData.contentType !== "image/png") {
          return encodeResponse(
            "400",
            "",
            "Content type of vCard mmust be 'image/png'"
          );
        }
      }
      return encodeResponse("200", vCardImageData, "vCard got");
    },
    documentPutDocument: async (
      passportUUID,
      resourceid,
      contentType,
      file,
      upload
    ) => {
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );
      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      resourceid = encodeURI(resourceid);
      contentType = encodeURI(contentType);

      const config = {
        headers: {
          "Content-Type": "multipart/form-data",
          passportuuid: passportUUID,
          resourceid,
          contentType,
          upload
        }
      };

      const response = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.documentPutDocument,
        config,
        file
      );

      if (response.code !== "200") {
        return encodeResponse("400", "", response.status);
      }

      return encodeResponse("200", response.data, "Document stored");
    },
    hasSession() {
      return new Penpal.Promise(result => {
        const authenticationPublicKey = localStorage.getItem(
          "authenticatedIdentity"
        );
        if (authenticationPublicKey === null) {
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
          });
        }
        if (window.loadedIdentities[authenticationPublicKey] === null) {
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
          });
        }

        const success = extendPinCodeTtl(authenticationPublicKey);

        if (success === false) {
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
          });
        }

        executeRestfulFunction(
          "private",
          viamApi,
          viamApi.identityHasSession,
          null
        ).then(executeResult => {
          result(executeResult);
        });
      });
    },
    marketingSignUpIdentificator(identificator, reference) {
      return new Penpal.Promise(result => {
        viamApi.setIdentity("marketingapppublickey");

        executeRestfulFunction(
          "public",
          viamApi,
          viamApi.marketingSignUpIdentificator,
          null,
          identificator,
          reference
        ).then(executeResult => {
          viamApi.setIdentity("");
          viamApi.setSessionData("", "");
          result(executeResult);
        });
      });
    },
    marketingGetIdentificatorProfile(identificator, pincode) {
      return new Penpal.Promise(result => {
        viamApi.setIdentity("marketingapppublickey");

        executeRestfulFunction(
          "public",
          viamApi,
          viamApi.marketingGetIdentificatorProfile,
          null,
          identificator,
          pincode
        ).then(executeResult => {
          viamApi.setIdentity("");
          viamApi.setSessionData("", "");
          result(executeResult);
        });
      });
    },
    marketingExecuteEventForIdentificator(identificator, pincode, event) {
      return new Penpal.Promise(result => {
        viamApi.setIdentity("marketingapppublickey");

        executeRestfulFunction(
          "public",
          viamApi,
          viamApi.marketingExecuteEventForIdentificator,
          null,
          identificator,
          pincode,
          event
        ).then(executeResult => {
          viamApi.setIdentity("");
          viamApi.setSessionData("", "");
          result(executeResult);
        });
      });
    },
    getCurrentlyAuthenticatedIdentity() {
      const {
        publicKey,
        x509Certificate
      } = window.currentlyAuthenticatedIdentity.authentication;

      return encodeResponse(
        "200",
        {
          authentication: {
            publicKey,
            x509Certificate
          }
        },
        "Currently authenticated identity"
      );
    },
    extractMessageID(mime) {
      return new Penpal.Promise(result => {
        result(extractMessageID(mime));
      });
    },
    stringToUtf8ByteArray(str) {
      return new Penpal.Promise(result => {
        result(stringToUtf8ByteArray(str));
      });
    },
    utf8ByteArrayToString(ba) {
      return new Penpal.Promise(result => {
        result(utf8ByteArrayToString(ba));
      });
    },
    stringToUtf8Base64(str) {
      return new Penpal.Promise(result => {
        result(stringToUtf8Base64(str));
      });
    },
    utf8Base64ToString(strBase64) {
      return new Penpal.Promise(result => {
        result(utf8Base64ToString(strBase64));
      });
    },
    base64ToByteArray(strBase64) {
      return new Penpal.Promise(result => {
        result(base64ToByteArray(strBase64));
      });
    },
    byteArrayToBase64(ba) {
      return new Penpal.Promise(result => {
        result(byteArrayToBase64(ba));
      });
    },
    hexStringToUtf8ByteArray(str) {
      return new Penpal.Promise(result => {
        result(hexStringToUtf8ByteArray(str));
      });
    },

    // Collabora APIs
    collaboraDiscovery() {
      return collaboraApi.discovery().then(apps => apps);
    },

    // WOPI
    getPassportsNewProtocol: async (resourceID, contentType) => {
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );

      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      const response = await wopiAPI.getPassportsNewProtocol(
        resourceID,
        contentType
      );
      return response.data;
    },

    getPassports: async fileId => {
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );

      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      const response = await wopiAPI.getPassports(fileId);
      return response.data;
    },

    wopiCreateDocument: async (passportUUID, path, title) => {
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );
      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      const config = {
        headers: {
          path,
          passportuuid: passportUUID,
          title
        }
      };
      const createDocumentResult = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.documentCreateDocument,
        config
      );

      if (createDocumentResult.code !== "200") {
        return createDocumentResult;
      }

      const resourceID = createDocumentResult.data;

      const accessTokenResponse = await wopiAPI.getAccessToken(
        passportUUID,
        resourceID
      );

      if (accessTokenResponse.data.code !== "200") {
        return accessTokenResponse.data;
      }

      const accessToken = accessTokenResponse.data.data;

      const result = {
        resourceID,
        accessToken
      };

      return encodeResponse("200", result, "ok");
    },

    wopiGetAccessToken: async (passportUUID, resourceID, contentType) => {
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );

      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      const response = await wopiAPI.getAccessToken(
        passportUUID,
        resourceID,
        contentType
      );
      return response.data;
    },

    wopiPutFile: async (resourceID, accessToken, file) => {
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );

      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      const response = await wopiAPI.putDocument(resourceID, accessToken, file);
      return response.data;
    }
  }
});

connection.promise.then(parent => {
  iframeParent = parent;

  if (!navigator.cookieEnabled) {
    console.warn("Cookie disabled. Can't start library.");
    return;
  }

  window.addEventListener("storage", event => {
    if (event.key === "authenticatedIdentity" && event.newValue === null) {
      const publicKey =
        window.currentlyAuthenticatedIdentity.authentication.publicKey;
      window.currentlyLoadedIdentity = null;
      window.currentlyAuthenticatedIdentity = null;
      const event = createEvent("LogoutFromAnotherTab", "Logout", [publicKey]);
      parent.onEvent(event);
    }
  });

  const identities = localStorage.getItem("identities");

  console.log("Library loaded at: " + new Date().toISOString());

  if (identities === "" || identities === null) {
    localStorage.setItem("identities", JSON.stringify({}));
  }

  if (
    localStorage.getItem("uuid") === null ||
    localStorage.getItem("token") === null ||
    localStorage.getItem("authenticatedIdentity") === null
  ) {
    const event = createEvent("", "NotAuthenticated");
    parent.onEvent(event);
    localStorage.removeItem("uuid");
    localStorage.removeItem("token");
    localStorage.removeItem("authenticatedIdentity");
  } else {
    const authenticationPublicKey = localStorage.getItem(
      "authenticatedIdentity"
    );
    const pinCode = getPincode(authenticationPublicKey);

    if (pinCode === "" || pinCode === null) {
      loadIdentityInternal(authenticationPublicKey, "00000000").then(result => {
        if (result.code !== "200") {
          const event = createEvent(
            "CanNotGetPincodeForAuthenticatedIdentity",
            "IdentityNotLoaded",
            [authenticationPublicKey]
          );
          parent.onEvent(event);
        }
      });
    } else {
      loadIdentityInternal(authenticationPublicKey, pinCode).then(result => {
        if (result.code !== "200") {
          const event = createEvent(
            "CanNotLoadIdentity",
            "ErrorDuringLoadingIdentity",
            [authenticationPublicKey]
          );
          parent.onEvent(event);
        }
      });
    }
  }

  let anynomousDeviceKeyEventsProcessing = false;
  let maxDeviceKeyAnonymousEventTime = 0;

  let eventsDeviceEventsProcessing = false;
  let maxDeviceKeyEventTime = 0;

  let eventsEntityEventsProcessing = false;
  let maxEntityEventTime = 0;

  let identityLoadedEvent = false;
  let identityAuthenticatedEvent = false;

  let previousLocalStorageUUID;
  let previousLocalStorageToken;
  let previousLocalStorageIdentity;

  setInterval(async function () {
    console.log(window.currentlyAuthenticatedIdentity);
    if (window.currentlyAuthenticatedIdentity) {
      console.log("has window.currentlyauthenitcated");
      const { authentication } = window.currentlyAuthenticatedIdentity;
      const pinCode = getPincode(authentication.publicKey);
      if (pinCode) {
        const identity = await getIdentityFromLocalStorage(
          authentication.publicKey,
          pinCode,
          false
        );

        window.currentlyLoadedIdentity = identity;

        if (!identityAuthenticatedEvent && identity) {
          const event = createEvent("IdentityAuthenticated", "Authenticated", [
            identity.authentication.publicKey
          ]);
          parent.onEvent(event);
          identityAuthenticatedEvent = true;
        }
      } else {
        const authenticationPublicKey = localStorage.getItem(
          "authenticatedIdentity"
        );

        if (authenticationPublicKey) {
          const result = await loadIdentityInternal(
            authenticationPublicKey,
            "00000000"
          );
          if (result.code !== "200") {
            const event = createEvent(
              "CanNotGetPincodeForAuthenticatedIdentity",
              "IdentityNotLoaded",
              [authenticationPublicKey]
            );
            parent.onEvent(event);
            clearPinCodeTtl(authenticationPublicKey);
            window.currentlyAuthenticatedIdentity = null;
          }
        }

        identityAuthenticatedEvent = false;
        window.currentlyLoadedIdentity = null;
      }

      localStorage.removeItem("currentlyLoadedIdentity");
    }

    if (window.currentlyLoadedIdentity) {
      const pinCode = getPincode(
        window.currentlyLoadedIdentity.authentication.publicKey
      );
      if (!pinCode) {
        if (!identityLoadedEvent) {
          const result = await loadIdentityInternal(
            window.currentlyLoadedIdentity.authentication.publicKey,
            "00000000"
          );
          if (window.currentlyLoadedIdentity && result.code !== "200") {
            const event = createEvent(
              "CanNotLoadPincodeForLoadedIdentity",
              "IdentityNotLoaded",
              [window.currentlyLoadedIdentity.authentication.publicKey]
            );
            parent.onEvent(event);
            identityLoadedEvent = true;
          }
        }
      } else {
        identityLoadedEvent = false;
      }
    }

    if (window.currentlyAuthenticatedIdentity) {
      const now = new Date().getTime();
      if (now - window.lastTimeGetProfile > 30000) {
        getProfileData(window.currentlyAuthenticatedIdentity);
        window.lastTimeGetProfile = now;
      }
    }

    const currentLocalStorageUUID = localStorage.getItem("uuid");
    const currentLocalStorageToken = localStorage.getItem("token");
    const currentLocalStorageIdentity = localStorage.getItem(
      "authenticatedIdentity"
    );
    if (
      (!currentLocalStorageUUID && previousLocalStorageUUID) ||
      (!currentLocalStorageToken && previousLocalStorageToken) ||
      (!currentLocalStorageIdentity && previousLocalStorageIdentity)
    ) {
      previousLocalStorageUUID = null;
      previousLocalStorageToken = null;
      previousLocalStorageIdentity = null;

      destroyAuthentication();

      const event = createEvent("", "LogoutExternal");
      parent.onEvent(event);
    } else {
      previousLocalStorageUUID = currentLocalStorageUUID;
      previousLocalStorageToken = currentLocalStorageToken;
      previousLocalStorageIdentity = currentLocalStorageIdentity;
    }
  }, 50);

  const getNewEventsWithoutSession = async () => {
    anynomousDeviceKeyEventsProcessing = true;
    try {
      const executeResult = await executeRestfulFunction(
        "public",
        viamAnonymousApi,
        viamAnonymousApi.eventGetNewEventsWithoutSession,
        null,
        "devicekey"
      );
      if (executeResult.code === "200") {
        const eventsLen = executeResult.data.length;
        let changedMaxDeviceKeyAnonymousEventTime = false;
        for (let i = 0; i < eventsLen; i++) {
          const event = executeResult.data[i];
          switch (event.type) {
            case "DeviceConfirmed": {
              await setIdentityInLocalStorage(window.currentlyLoadedIdentity);
              parent.onEvent(event);
              break;
            }

            case "QRCodeUpdated": {
              const actionID = event["actionID"];
              const QrCode = event["payloads"][1];

              const eventCopy = JSON.parse(JSON.stringify(event));

              const dataUrl = await generateQrCode(actionID + "," + QrCode);
              eventCopy["payloads"].push(dataUrl);
              parent.onEvent(eventCopy);
              break;
            }

            case "KeyDeleted": {
              const authenticationPublicKey = localStorage.getItem(
                "authenticatedIdentity"
              );
              clearPinCodeTtl(authenticationPublicKey);
              localStorage.removeItem("uuid");
              localStorage.removeItem("token");
              localStorage.removeItem("authenticatedIdentity");
              delete window.loadedIdentities[authenticationPublicKey];
              window.currentlyLoadedIdentity = null;
              window.currentlyAuthenticatedIdentity = null;
              window.lastTimeGetProfile = 0;

              destroyIdentityFromLocalStorage(authenticationPublicKey);
              break;
            }

            default: {
              parent.onEvent(event);
            }
          }
          changedMaxDeviceKeyAnonymousEventTime = true;
          maxDeviceKeyAnonymousEventTime = Math.max(
            maxDeviceKeyAnonymousEventTime,
            event.stamp
          );
        }

        if (changedMaxDeviceKeyAnonymousEventTime) {
          await executeRestfulFunction(
            "public",
            viamAnonymousApi,
            viamAnonymousApi.eventUpdateLastViewedWithoutSession,
            null,
            "devicekey",
            maxDeviceKeyAnonymousEventTime.toString()
          );
        }
      }
    } catch (e) {
      console.warn(e);
    }
    anynomousDeviceKeyEventsProcessing = false;
  };

  const getNewDeviceEvents = async () => {
    eventsDeviceEventsProcessing = true;
    try {
      const executeResult = await executeRestfulFunction(
        "private",
        viamApi,
        viamApi.eventGetNewEvents,
        null,
        "devicekey"
      );
      if (executeResult.code === "200") {
        const eventsLen = executeResult.data.length;
        const changedMaxDeviceKeyEventTime = false;
        for (let i = 0; i < eventsLen; i++) {
          const event = executeResult.data[i];
          if (event.type === "QRCodeUpdated") {
            const actionID = event["actionID"];
            const QrCode = event["payloads"][1];

            const eventCopy = JSON.parse(JSON.stringify(event));

            const dataUrl = await generateQrCode(actionID + "," + QrCode);
            eventCopy["payloads"].push(dataUrl);
            parent.onEvent(eventCopy);
          } else {
            parent.onEvent(event);
          }
          maxDeviceKeyEventTime = Math.max(maxDeviceKeyEventTime, event.stamp);
        }
        if (changedMaxDeviceKeyEventTime) {
          await executeRestfulFunction(
            "private",
            viamApi,
            viamApi.eventUpdateLastViewed,
            null,
            "devicekey",
            maxDeviceKeyEventTime.toString()
          );
        }
      }
    } catch (e) {
      console.warn(e);
    }
    eventsDeviceEventsProcessing = false;
  };

  const getNewEntityEvents = async () => {
    eventsEntityEventsProcessing = true;
    try {
      const executeResult = await executeRestfulFunction(
        "private",
        viamApi,
        viamApi.eventGetNewEvents,
        null,
        "entity"
      );

      if (executeResult.code === "200") {
        const eventsLen = executeResult.data.length;
        let changedMaxEntityEventTime = false;
        for (let i = 0; i < eventsLen; i++) {
          const event = executeResult.data[i];
          if (event.type === "QRCodeUpdated") {
            const actionID = event["actionID"];
            const QrCode = event["payloads"][1];

            const eventCopy = JSON.parse(JSON.stringify(event));

            const dataUrl = await generateQrCode(actionID + "," + QrCode);
            eventCopy["payloads"].push(dataUrl);
            parent.onEvent(eventCopy);
            continue;
          }

          parent.onEvent(event);
          changedMaxEntityEventTime = true;
          maxEntityEventTime = Math.max(maxEntityEventTime, event.stamp);
        }
        if (changedMaxEntityEventTime) {
          await executeRestfulFunction(
            "private",
            viamApi,
            viamApi.eventUpdateLastViewed,
            null,
            "entity",
            maxEntityEventTime.toString()
          );
        }
      }
    } catch (e) {
      console.warn(e);
    }
    eventsEntityEventsProcessing = false;
  };

  setInterval(() => {
    if (
      window.currentlyLoadedIdentity &&
      !anynomousDeviceKeyEventsProcessing &&
      !window.currentlyAuthenticatedIdentity
    ) {
      getNewEventsWithoutSession();
    }

    if (window.currentlyAuthenticatedIdentity) {
      // These functions has to be executed at the same time.
      !eventsDeviceEventsProcessing && getNewDeviceEvents();
      !eventsEntityEventsProcessing && getNewEntityEvents();
    }
  }, 1000);
});