Skip to content
Snippets Groups Projects
viamapi-iframe.js 47.75 KiB
import { parseSMIME } from '../utilities/emailUtilities';

const QRCode = require('qrcode');
const Penpal = require('penpal').default;

import {
  createDeviceHash,
  destroyIdentityFromLocalStorage,
  encodeResponse,
  listIdentitiesFromLocalStorage, makeid
} from '../utilities/appUtility';
import {LOGIN_MODES} from '../constants/authentication';
import {
  createOneTimePassportCertificate,
  createPassportCertificate,
  decryptMessage,
  encryptMessage, signEmail
} from '../utilities/signingUtilities';
import CryptoData from '../CryptoData';
import Identity from '../Identity';
import {STATUS_DEVICE_REVOKED} from '../constants/statuses';

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

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

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

  var color = identityColors[colorIndex];

  colorIndex++;

  colorIndex = colorIndex % identityColors.length;

  localStorage.setItem("colorIndex", colorIndex);

  return color
}

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

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

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

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

  return storedColor
}

function setIdentityInLocalStorage(identityToStore, extendKey = true) {
  var 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) => {
    var success = true;
    if(extendKey === true) {
      success = extendPinCodeTtl(key, pinCode)
    }
    if (success === true) {
      localStorage.setItem(key, encryptedIdentity);
      let serializedIdentitiesList = localStorage.getItem("identities");
      let 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") {
        var 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 === "") {
    var now = new Date();
    var nowMillis = now.getTime();
    var ttl = window.sessionStorage.getItem("pincodettls/" + key);
    if (ttl == null || ttl === "" || nowMillis >= parseInt(ttl)) {
      clearPinCodeTtl(key);
      return false
    } else {
      var ttl = now.getTime() + 4 * 60 * 60 * 1000;
      window.sessionStorage.setItem("pincodettls/" + key, ttl);
    }
  } else {
    var now = new Date();
    var 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) {
  var now = new Date();
  var nowMillis = now.getTime();
  var 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": type,
    "stamp": new Date().getTime(),
    "payloads" : 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) => {
  const { loadedIdentities, viamApi } = window;
  const { publicKey } = identity.authentication;

  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;
  const response = await fn.apply(that, [config, ...args]);

  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 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);
  const { data } = await fn.apply(that, [config, ...args]);
  return data;
}

window.executeRestfulFunction = executeRestfulFunction;
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 changeIdentityPinCodeInternal(key, oldPinCode, newPinCode) {

  return new Penpal.Promise(result => {
    getIdentityFromLocalStorage(key, oldPinCode, false).then((identity) => {

      identity.pinCode = newPinCode;

      setIdentityInLocalStorage(identity).then(() => {
        result({
          "data": "",
          "code": "200",
          "status": "Successfully changed pincode"
        });
      }).catch((e) => {
        result({
          "data": "",
          "code": "400",
          "status": "Cannot store identity " + e
        });
      });
    }).catch((e) => {
      result({
        "data": "",
        "code": "400",
        "status": "Cannot get identity " + 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;
    var passport = passportIdentity.getPassport(passportUUID);
    if(passport === undefined || passport === null) {
      createPassportCertificate(passportUUID).then(function(keys){
        var cryptoData = new CryptoData();
        cryptoData.setPublicKey(keys["publicKeyPEM"]);
        cryptoData.setPrivateKey(keys["privateKeyPEM"]);
        var 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") {
            var signedCertificate = atob(executeResult.data["SignedCertificate"]);
            //download("passportCertificateAfterSigning.crt", "text/plain", signedCertificate)
            var keyUUID = executeResult.data["CertificateUUID"];
            var encodedChain = executeResult.data["Chain"];
            //download("rootCertificate.crt", "text/plain", atob(encodedChain[0]))

            var chain = [];

            for(var 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 {
      var 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){
          var newIdentity = new Identity();
          var cryptoData = new CryptoData();
          cryptoData.setPublicKey(keys["publicKeyPEM"]);
          cryptoData.setPrivateKey(keys["privateKeyPEM"]);
          cryptoData.setx509Certificate(keys["certificatePEM"]);
          newIdentity.setAuthentication(cryptoData);
          newIdentity.setPinCode(pinCode);

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

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

          result({"data" : newIdentity,
            "code" : "200",
            "status" : "Identity created"
          })
        });
      })
    },
    listIdentities() {
      return new Penpal.Promise(result => {
        var identities = listIdentitiesFromLocalStorage();
        result({"data" : identities,
          "code" : "200",
          "status" : "Identities listed"
        })
      });
    },
    loadIdentity(identityKey, pinCode) {
      return loadIdentityInternal(identityKey, pinCode)
    },
    changeIdentityPinCode(key, oldPinCode, newPinCode) {
      return changeIdentityPinCodeInternal(key, oldPinCode, newPinCode)
    },
    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);
        });
      });
    },
    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.SMS || mode === LOGIN_MODES.PREVIOUSLY_ADDED_DEVICE) {
          handleIdentityLogin(loginIdentity, data.Uuid, data.Session);
          await getProfileData(loginIdentity);

          if (mode === LOGIN_MODES.SMS) {
            await setIdentityInLocalStorage(loginIdentity);
          }
        } else if (mode === LOGIN_MODES.NEW_DEVICE) {
          const dataUrl = await QRCode.toDataURL(`${data.ActionID},${data.QrCode}`);
          Object.assign(responseToClient.data, { image: dataUrl });
        }
      }

      return responseToClient;
    },
    identityPullAvatarFromGravatar: async () => {
      const authenticationPublicKey = localStorage.getItem("authenticatedIdentity");

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

      return await executeRestfulFunction("private", viamApi, viamApi.identityPullAvatarFromGravatar);
    },
    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"
          })
        }

        var success = extendPinCodeTtl(authenticationPublicKey);

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

        executeRestfulFunction("private", viamApi, viamApi.identityAddNewDevice, null).then(executeResult => {
          if (executeResult.code === "200") {
            var actionID = executeResult.data["ActionID"];
            var QrCode = executeResult.data["QrCode"];
            QRCode.toDataURL(actionID + "," + QrCode, function (err, url) {
              executeResult.data["image"] = url;
              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"
          })
        }

        var 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 () => {
      const authenticationPublicKey = localStorage.getItem("authenticatedIdentity");
      if (!authenticationPublicKey || !window.loadedIdentities[authenticationPublicKey]) {
        return {
          data: "",
          code: "400",
          status: "Identity not loaded"
        };
      }

      const identityLogoutResponse = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.identityLogout,
        null
      );

      destroyAuthentication();

      return identityLogoutResponse;
    },
    identityRestoreAccess(restoreAccessIdentity, identificator) {
      return new Penpal.Promise(result => {
        viamApi.setSessionData("", "");
        viamApi.setIdentity(restoreAccessIdentity.authentication.publicKey);

        executeRestfulFunction("public", viamApi, viamApi.identityRestoreAccess, null, identificator).then(executeResult => {
            result(executeResult);
        });
      });
    },
    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"
          }
        }

        var 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"
          }
        }

        var 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"
          }
        }

        var success = extendPinCodeTtl(authenticationPublicKey);

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

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

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

              var 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"
            })
          }
        })
      });
    },
    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");
    },
    documentCreateDocument: async (path, passportUUID, contenttype) => {
      const authenticationPublicKey = localStorage.getItem("authenticatedIdentity");
      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

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

      return encodeResponse("200", response.data, "Document created");
    },
    documentPutDocument: async (passportUUID, resourceid, file) => {
      const authenticationPublicKey = localStorage.getItem("authenticatedIdentity");
      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

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

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

      return encodeResponse("200", response.data, "Document created");
    },
    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"
          });
        }

        var 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"
      );
    },
    stringToUtf8ByteArray(str) {
      if (typeof str !== 'string') {
        str = str.toString()
      }
      let res = Buffer.from(str,'utf-8');
      return new Penpal.Promise(result => {
        result(res)
      })
    },
    utf8ByteArrayToString(ba) {
      if (!Buffer.isBuffer(ba)) {
        ba = Buffer.from(ba)
      }
      let res = ba.toString('utf-8');
      return new Penpal.Promise(result => {
        result(res)
      })
    },
    stringToUtf8Base64(str) {
      if (!Buffer.isBuffer(str)) {
        if (typeof str !== 'string') {
          str = str.toString()
        }
        str = Buffer.from(str, 'utf-8')
      }
      let res = str.toString('base64');
      return new Penpal.Promise(result => {
        result(res)
      })
    },
    utf8Base64ToString(strBase64) {
      if (!Buffer.isBuffer(strBase64)) {
        if (typeof strBase64 !== 'string') {
          strBase64 = strBase64.toString()
        }
        strBase64 = Buffer.from(strBase64, 'base64')
      }
      let res = strBase64.toString('utf-8');
      return new Penpal.Promise(result => {
        result(res)
      })
    },
    base64ToByteArray(strBase64) {
      if (typeof strBase64 !== 'string') {
        strBase64 = strBase64.toString()
      }
      let res = Buffer.from(strBase64, 'base64');
      return new Penpal.Promise(result => {
        result(res)
      })
    },
    byteArrayToBase64(ba) {
      if (!Buffer.isBuffer(ba)) {
        ba = Buffer.from(ba)
      }
      let res = ba.toString('base64');
      return new Penpal.Promise(result => {
        result(res)
      })
    },

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

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

    wopiPutFile: async (path, 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(path, 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
  ) {
    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;

  setInterval(async function () {
    if (window.currentlyAuthenticatedIdentity) {
      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;
      }
    }

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

  setInterval(async () => {
    if (window.currentlyLoadedIdentity && !anynomousDeviceKeyEventsProcessing && !window.currentlyAuthenticatedIdentity) {
      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 "Authenticated" : {
                const uuid = event.payloads[0];
                const token = event.payloads[1];
                handleIdentityLogin(window.currentlyLoadedIdentity, uuid, token);
                const identityToStore = window.currentlyAuthenticatedIdentity;
                event.payloads = [{fromQRCode: true}];
                await setIdentityInLocalStorage(identityToStore);
                await getProfileData(identityToStore);
                parent.onEvent(event);
                break;
              }

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

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

                QRCode.toDataURL(actionID + "," + QrCode, function (err, url) {
                  eventCopy["payloads"].push(url);
                  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;
    }

    if (window.currentlyAuthenticatedIdentity != null && eventsDeviceEventsProcessing === false) {
      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));

              QRCode.toDataURL(actionID + "," + QrCode, function (err, url) {
                eventCopy["payloads"].push(url);
                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;
    }

    if (window.currentlyAuthenticatedIdentity != null && eventsEntityEventsProcessing === false) {
      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));

              QRCode.toDataURL(actionID + "," + QrCode, function (err, url) {
                eventCopy["payloads"].push(url);
                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;
    }
  }, 1000);
});