import _defineProperty from '@babel/runtime/helpers/defineProperty';
import { TkeyError, ShareStore, prettyPrintError } from '@tkey-singularity/common-types';
import { encrypt, getPublic, decrypt, generatePrivate } from '@toruslabs/eccrypto';
import { ec } from '@toruslabs/metadata-helpers';
import BN from 'bn.js';
import axios from 'axios';

class DeviceEncryptedMessage {
  constructor(ciphertext, ephemPublicKey, iv, mac) {
    _defineProperty(this, "ciphertext", void 0);
    _defineProperty(this, "ephemPublicKey", void 0);
    _defineProperty(this, "iv", void 0);
    _defineProperty(this, "mac", void 0);
    this.ciphertext = ciphertext;
    this.ephemPublicKey = ephemPublicKey;
    this.iv = iv;
    this.mac = mac;
  }
  static fromJSON(value) {
    const {
      ciphertext,
      ephemPublicKey,
      iv,
      mac
    } = value;
    return new DeviceEncryptedMessage(ciphertext, ephemPublicKey, iv, mac);
  }
  toJSON() {
    return {
      ciphertext: this.ciphertext.toString(),
      ephemPublicKey: this.ephemPublicKey.toString(),
      iv: this.iv.toString(),
      mac: this.mac.toString()
    };
  }
}

class WebStorageError extends TkeyError {
  constructor(code, message) {
    // takes care of stack and proto
    super(code, message);
    // Set name explicitly as minification can mangle class names
    Object.defineProperty(this, "name", {
      value: "WebStorageError"
    });
  }
  static fromCode(code) {
    let extraMessage = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "";
    return new WebStorageError(code, `${WebStorageError.messages[code]}${extraMessage}`);
  }
  static default() {
    let extraMessage = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "";
    return new WebStorageError(3000, `${WebStorageError.messages[3000]}${extraMessage}`);
  }
  // Custom methods
  static unableToReadFromStorage() {
    let extraMessage = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "";
    return WebStorageError.fromCode(3101, extraMessage);
  }
  static shareUnavailableInFileStorage() {
    let extraMessage = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "";
    return WebStorageError.fromCode(3201, extraMessage);
  }
  static fileStorageUnavailable() {
    let extraMessage = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "";
    return WebStorageError.fromCode(3202, extraMessage);
  }
  static localStorageUnavailable() {
    let extraMessage = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "";
    return WebStorageError.fromCode(3301, extraMessage);
  }
  static shareUnavailableInLocalStorage() {
    let extraMessage = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "";
    return WebStorageError.fromCode(3302, extraMessage);
  }
}
_defineProperty(WebStorageError, "messages", {
  3000: "default",
  // module
  3101: "unableToReadFromStorage",
  // fileStorage
  3201: "No Share exists in file system",
  3202: "No requestFileSystem",
  // localstorage
  3301: "Local storage is not enabled",
  3302: "No share exists in localstorage"
});

function getWindow() {
  if (typeof window !== "undefined") return window;
  if (typeof self !== "undefined") return self;
  throw new Error("Unable to locate window object.");
}
function encParamsHexToBuf(encParamsHex) {
  return {
    iv: Buffer.from(encParamsHex.iv, "hex"),
    ephemPublicKey: Buffer.from(encParamsHex.ephemPublicKey, "hex"),
    ciphertext: Buffer.from(encParamsHex.ciphertext, "hex"),
    mac: Buffer.from(encParamsHex.mac, "hex")
  };
}
function encParamsBufToHex(encParams) {
  return {
    iv: Buffer.from(encParams.iv).toString("hex"),
    ephemPublicKey: Buffer.from(encParams.ephemPublicKey).toString("hex"),
    ciphertext: Buffer.from(encParams.ciphertext).toString("hex"),
    mac: Buffer.from(encParams.mac).toString("hex")
  };
}
async function encryptData(privKeyHex, d) {
  const serializedDec = JSON.stringify(d);
  const serializedBuf = Buffer.from(serializedDec, "utf-8");
  const encParams = await encrypt(getPublic(Buffer.from(privKeyHex, "hex")), serializedBuf);
  const encParamsHex = encParamsBufToHex(encParams);
  const sData = JSON.stringify(encParamsHex);
  return sData;
}
async function decryptData(privKeyHex, d) {
  const encParamsHex = JSON.parse(d);
  const encParams = encParamsHexToBuf(encParamsHex);
  const keyPair = ec.keyFromPrivate(privKeyHex);
  const serializedBuf = await decrypt(Buffer.from(keyPair.getPrivate().toString("hex", 64), "hex"), encParams);
  const serializedDec = serializedBuf.toString("utf-8");
  const data = JSON.parse(serializedDec);
  return data;
}

// Web Specific declarations
const requestedBytes = 1024 * 1024 * 10; // 10MB
const win$1 = getWindow();
win$1.requestFileSystem = win$1.requestFileSystem || win$1.webkitRequestFileSystem;
function download(filename, text) {
  const element = document.createElement("a");
  element.setAttribute("href", `data:application/json;charset=utf-8,${encodeURIComponent(text)}`);
  element.setAttribute("download", filename);
  element.style.display = "none";
  document.body.appendChild(element);
  element.click();
  document.body.removeChild(element);
}
async function requestQuota() {
  return new Promise((resolve, reject) => {
    navigator.webkitPersistentStorage.requestQuota(requestedBytes, resolve, reject);
  });
}
async function browserRequestFileSystem(grantedBytes) {
  return new Promise((resolve, reject) => {
    win$1.requestFileSystem(win$1.PERSISTENT, grantedBytes, resolve, reject);
  });
}
async function getFile(fs, path, create) {
  return new Promise((resolve, reject) => {
    fs.root.getFile(path, {
      create
    }, data => resolve(data), reject);
  });
}
async function readFile(fileEntry) {
  return new Promise((resolve, reject) => {
    fileEntry.file(resolve, reject);
  });
}
const getShareFromFileStorage = async key => {
  if (win$1.requestFileSystem) {
    const fs = await browserRequestFileSystem(requestedBytes);
    const fileEntry = await getFile(fs, key, false);
    const file = await readFile(fileEntry);
    const fileStr = await file.text();
    if (!fileStr) {
      throw WebStorageError.shareUnavailableInFileStorage();
    }
    return ShareStore.fromJSON(JSON.parse(fileStr));
  }
  throw WebStorageError.fileStorageUnavailable();
};
const storeShareOnFileStorage = async (share, key) => {
  // if we're on chrome (thus window.requestFileSystem exists) we use it
  const fileName = `${key}.json`;
  const fileStr = JSON.stringify(share);
  if (win$1.requestFileSystem) {
    const grantedBytes = await requestQuota();
    const fs = await browserRequestFileSystem(grantedBytes);
    const fileEntry = await getFile(fs, key, true);
    await new Promise((resolve, reject) => {
      fileEntry.createWriter(fileWriter => {
        fileWriter.onwriteend = resolve;
        fileWriter.onerror = reject;
        const bb = new Blob([fileStr], {
          type: "application/json"
        });
        fileWriter.write(bb);
      }, reject);
    });
  } else {
    // we make the user download a file
    download(fileName, fileStr);
  }
};
const canAccessFileStorage = async () => navigator.permissions.query({
  name: "persistent-storage"
});

const win = getWindow();
const localStorageKey = "KEY_FOR_PRIVATE_KEY";
function storageAvailable(type) {
  let storage;
  try {
    storage = win[type];
    const x = "__storage_test__";
    storage.setItem(x, x);
    storage.removeItem(x);
    return true;
  } catch (e) {
    return e instanceof DOMException && (
    // everything except Firefox
    e.code === 22 ||
    // Firefox
    e.code === 1014 ||
    // test name field too, because code might not be present
    // everything except Firefox
    e.name === "QuotaExceededError" ||
    // Firefox
    e.name === "NS_ERROR_DOM_QUOTA_REACHED") &&
    // acknowledge QuotaExceededError only if there's something already stored
    storage && storage.length !== 0;
  }
}
const storeShareOnLocalStorage = async (share, key) => {
  const fileStr = JSON.stringify(share);
  if (!storageAvailable("localStorage")) {
    throw WebStorageError.localStorageUnavailable();
  }
  win.localStorage.setItem(key, fileStr);
};
const storePrivateKeyOnLocalStorage = async (privateKey, verifier) => {
  if (!storageAvailable("localStorage")) {
    throw WebStorageError.localStorageUnavailable();
  }
  win.localStorage.setItem(verifier, privateKey);
};
const getPrivateKeyFromLocalStorage = async key => {
  if (!storageAvailable("localStorage")) {
    throw WebStorageError.localStorageUnavailable();
  }
  const foundKey = win.localStorage.getItem(key);
  if (!foundKey) throw WebStorageError.shareUnavailableInLocalStorage();
  return foundKey;
};
const getShareFromLocalStorage = async key => {
  if (!storageAvailable("localStorage")) {
    throw WebStorageError.localStorageUnavailable();
  }
  const foundFile = win.localStorage.getItem(key);
  if (!foundFile) throw WebStorageError.shareUnavailableInLocalStorage();
  return ShareStore.fromJSON(JSON.parse(foundFile));
};
const storeKeyOnLocalStorage = privateKey => {
  if (!storageAvailable("localStorage")) {
    throw WebStorageError.localStorageUnavailable();
  }
  win.localStorage.setItem(localStorageKey, privateKey);
};

async function post(url, requestBody, requestHeaders) {
  const response = await axios.post(url, requestBody, requestHeaders);
  return response;
}

const WEB_STORAGE_MODULE_NAME = "webStorage";
class WebStorageModule {
  constructor() {
    let canUseFileStorage = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
    _defineProperty(this, "moduleName", void 0);
    _defineProperty(this, "tbSDK", void 0);
    _defineProperty(this, "canUseFileStorage", void 0);
    this.moduleName = WEB_STORAGE_MODULE_NAME;
    this.canUseFileStorage = canUseFileStorage;
    this.setFileStorageAccess();
  }
  async setFileStorageAccess() {
    try {
      const result = await canAccessFileStorage();
      if (result.state === "denied") {
        this.canUseFileStorage = false;
      } else if (result.state === "granted") {
        this.canUseFileStorage = true;
      }
      // eslint-disable-next-line @typescript-eslint/no-this-alias
      const self = this;
      result.onchange = function permissionChange() {
        if (this.state === "denied") {
          self.canUseFileStorage = false;
        } else if (this.state === "granted") {
          self.canUseFileStorage = true;
        }
      };
    } catch (error) {}
  }
  setModuleReferences(tbSDK) {
    this.tbSDK = tbSDK;
    this.tbSDK._setDeviceStorage(this.storeDeviceShare.bind(this));
  }
  // eslint-disable-next-line
  async initialize() {}
  async storeDeviceShare(url, authDetails, deviceShareStore, customDeviceInfo) {
    const encryptedShare = await this.encryptShareWithPublicKey(deviceShareStore);
    await storePrivateKeyOnLocalStorage(encryptedShare.privateKey.toString("hex"), authDetails.verifier);
    // await this.storeCookieShare(encryptedShare, authDetails.verifier, authDetails.apiKey, authDetails.bearerToken, url);
    const shareDescription = {
      module: this.moduleName,
      userAgent: navigator.userAgent,
      dateAdded: Date.now()
    };
    if (customDeviceInfo) {
      shareDescription.customDeviceInfo = JSON.stringify(customDeviceInfo);
    }
    await this.tbSDK.addShareDescription(deviceShareStore.share.shareIndex.toString("hex"), JSON.stringify(shareDescription), true);
    return encryptedShare;
  }
  async storeDeviceShareOnFileStorage(shareIndex) {
    const metadata = this.tbSDK.getMetadata();
    const tkeypubx = metadata.pubKey.x.toString("hex");
    const shareStore = this.tbSDK.outputShareStore(new BN(shareIndex));
    return storeShareOnFileStorage(shareStore, tkeypubx);
  }
  async getDeviceShare(encryptedMessage, verifier) {
    let shareStore;
    try {
      shareStore = await this.getCookieShare(encryptedMessage, verifier);
    } catch (localErr) {
      throw WebStorageError.unableToReadFromStorage(`Error inputShareFromWebStorage: ${prettyPrintError(localErr)}`);
    }
    return shareStore;
  }
  async inputShareFromWebStorage(authDetails, encryptedMessage) {
    const shareStore = await this.getCookieShare(encryptedMessage, authDetails.verifier);
    this.tbSDK.inputShareStore(shareStore);
    return {
      latestShareStore: shareStore
    };
  }
  async encryptShareWithPublicKey(share) {
    const privateKey = generatePrivate();
    const privKeyHex = privateKey.toString("hex");
    const publicKey = getPublic(privateKey);
    const encryptedShareMessage = await encryptData(privKeyHex, share);
    return {
      publickey: publicKey,
      privateKey,
      encryptedShare: encryptedShareMessage
    };
  }
  async getCookieShare(encryptedString, verifier) {
    const privateKeyHex = await getPrivateKeyFromLocalStorage(verifier);
    const shareStore = await decryptData(privateKeyHex, encryptedString);
    return shareStore;
  }
  async storeCookieShare(encryptedShare, verifier, apikey, bearerToken, url) {
    const body = JSON.stringify({
      encryptedShare
    });
    const header = {
      credentials: "include",
      withCredentials: true,
      crossDomain: true,
      "x-api-key": apikey,
      authorization: `Bearer ${bearerToken}`,
      "Content-Type": "application/json"
    };
    return post(url, body, header);
  }
  async getMetaData(url, apikey, bearerToken) {
    const body = "";
    const header = {
      credentials: "include",
      withCredentials: true,
      crossDomain: true,
      "x-api-key": apikey,
      authorization: `Bearer ${bearerToken}`,
      "Content-Type": "application/json"
    };
    return post(url, body, header);
  }
  async getMetaDataFlow(url, apikey, bearerToken) {
    const metadata = this.getMetaData(url, apikey, bearerToken);
    if (metadata == null) {
      const sdkMetadata = this.tbSDK.getMetadata();
      await this.storeMetadata(url, apikey, bearerToken, sdkMetadata);
    } else {
      return metadata;
    }
  }
  async storeMetadata(url, apikey, bearerToken, sdkMetadata) {
    const body = JSON.stringify(sdkMetadata);
    const header = {
      credentials: "include",
      withCredentials: true,
      crossDomain: true,
      "x-api-key": apikey,
      authorization: `Bearer ${bearerToken}`,
      "Content-Type": "application/json"
    };
    return post(url, body, header);
  }
}

export { DeviceEncryptedMessage, WEB_STORAGE_MODULE_NAME, WebStorageError, WebStorageModule, canAccessFileStorage, WebStorageModule as default, getPrivateKeyFromLocalStorage, getShareFromFileStorage, getShareFromLocalStorage, storeKeyOnLocalStorage, storePrivateKeyOnLocalStorage, storeShareOnFileStorage, storeShareOnLocalStorage };
//# sourceMappingURL=webStorage.esm.js.map
