text: import { bufferToStr, strToBuffer, delay } from './utils';
  // import { ECE_RECORD_SIZE } from './ece';
  const ECE_RECORD_SIZE = 1024 * 64;
  
  export class ConnectionError extends Error {
      constructor(cancelled, duration, size) {
          super(cancelled ? '0' : 'connection closed');
          this.cancelled = cancelled;
          this.duration = duration;
          this.size = size;
      }
  }
  
  let apiUrlPrefix = '';
  export function getApiUrl(path) {
      return apiUrlPrefix + path;
  }
  
  export function setApiUrlPrefix(prefix) {
      apiUrlPrefix = prefix;
  }
  
  function post(obj, bearerToken) {
      const h = {
          'Content-Type': 'application/json'
      };
      if (bearerToken) {
          h['Authentication'] = `Bearer ${bearerToken}`;
      }
      return {
          method: 'POST',
          headers: new Headers(h),
          body: JSON.stringify(obj)
      };
  }
  
  export function parseNonce(header) {
      header = header || '';
      return header.split(' ')[1];
  }
  
  async function fetchWithAuth(url, params, keychain) {
      const result = {};
      params = params || {};
      const h = await keychain.authHeader();
      params.headers = new Headers({
          Authorization: h,
          'Content-Type': 'application/json'
      });
      const response = await fetch(url, params);
      result.response = response;
      result.ok = response.ok;
      const nonce = parseNonce(response.headers.get('WWW-Authenticate'));
      result.shouldRetry = response.status === 401 && nonce !== keychain.nonce;
      keychain.nonce = nonce;
      return result;
  }
  
  async function fetchWithAuthAndRetry(url, params, keychain) {
      const result = await fetchWithAuth(url, params, keychain);
      if (result.shouldRetry) {
          return fetchWithAuth(url, params, keychain);
      }
      return result;
  }
  
  export async function del(id, owner_token) {
      if (typeof(id) == "string") {
          id = [id]
      }
      if (typeof(owner_token) == "string") {
          owner_token = [owner_token]
      }
      
      const response = await fetch(
          getApiUrl(`/api/delete`),
          post({
              id,
              owner_token
          })
      );
      return response.ok;
  }
  
  export async function setParams(id, owner_token, bearerToken, params) {
      const response = await fetch(
          getApiUrl(`/api/params/${id}`),
          post({
                  owner_token,
                  dlimit: params.dlimit
              },
              bearerToken
          )
      );
      return response.ok;
  }
  
  export async function fileInfo(id, owner_token) {
      if (typeof(id) == "string") {
          id = [id]
      }
      if (typeof(owner_token) == "string") {
          owner_token = [owner_token]
      }
  
      const response = await fetch(
          getApiUrl(`/api/info`),
          post({
              id,
              owner_token
          })
      );
  
      if (response.ok) {
          const obj = await response.json();
          return obj;
      }
  
      throw new Error(response.status);
  }
  
  export async function metadata(id, keychain) {
      const result = await fetchWithAuthAndRetry(
          getApiUrl(`/api/metadata/${id}`), {
              method: 'GET'
          },
          keychain
      );
      if (result.ok) {
          const data = await result.response.json();
          const meta = await keychain.decryptMetadata(strToBuffer(data.metadata));
          return {
              size: meta.size,
              ttl: data.ttl,
              iv: meta.iv,
              name: meta.name,
              type: meta.type,
              manifest: meta.manifest
          };
      }
      throw new Error(result.response.status);
  }
  
  export async function setPassword(id, owner_token, keychain) {
      const auth = await keychain.authKeyB64();
      const response = await fetch(
          getApiUrl(`/api/password/${id}`),
          post({
              owner_token,
              auth
          })
      );
      return response.ok;
  }
  
  function asyncInitWebSocket(server, token) {
      return new Promise((resolve, reject) => {
          try {
              const ws = new WebSocket(server);
              ws.addEventListener('open', () => resolve(ws), {
                  once: true
              });
          } catch (e) {
              reject(new ConnectionError(false));
          }
      });
  }
  
  function listenForResponse(ws, canceller) {
      return new Promise((resolve, reject) => {
          function handleClose(event) {
              // a 'close' event before a 'message' event means the request failed
              ws.removeEventListener('message', handleMessage);
              reject(new ConnectionError(canceller.cancelled));
          }
  
          function handleMessage(msg) {
              ws.removeEventListener('close', handleClose);
              try {
                  const response = JSON.parse(msg.data);
                  if (response.error) {
                      throw new Error(response.error);
                  } else {
                      resolve(response);
                  }
              } catch (e) {
                  reject(e);
              }
          }
          ws.addEventListener('message', handleMessage, {
              once: true
          });
          ws.addEventListener('close', handleClose, {
              once: true
          });
      });
  }
  
  async function upload(
      stream,
      metadata,
      verifierB64,
      timeLimit,
      dlimit,
      pwd,
      token,
      onprogress,
      canceller
  ) {
      let size = 0;
      const start = Date.now();
      const host = window.location.hostname;
      const port = window.location.port;
      const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
      const endpoint = `${protocol}//${host}${port ? ':' : ''}${port}/api/ws`;
      const ws = await asyncInitWebSocket(endpoint, token);
  
      try {
          const metadataHeader = bufferToStr(new Uint8Array(metadata));
          const fileMeta = {
              fileMetadata: metadataHeader,
              authorization: `send-v1 ${verifierB64}`,
              has_password: pwd,
              timeLimit,
              dlimit
          };
          const uploadInfoResponse = listenForResponse(ws, canceller);
          ws.send(JSON.stringify(fileMeta));
          const uploadInfo = await uploadInfoResponse;
  
          const completedResponse = listenForResponse(ws, canceller);
  
          const reader = stream.getReader();
          let state = await reader.read();
  
          while (!state.done) {
              if (canceller.cancelled) {
                  ws.close();
              }
              if (ws.readyState !== WebSocket.OPEN) {
                  break;
              }
              const buf = state.value;
              ws.send(buf);
              onprogress(size);
              size += buf.length;
              state = await reader.read();
  
              while (
                  ws.bufferedAmount > buf.length &&
                  ws.readyState === WebSocket.OPEN &&
                  !canceller.cancelled
              ) {
                  await delay();
              }
          }
  
          if (ws.readyState === WebSocket.OPEN) {
              ws.send(new Uint8Array([0])); //EOF
          }
  
          await completedResponse;
          uploadInfo.duration = Date.now() - start;
          const estimateSpeed = size / uploadInfo.duration * 1000
          // console.log(uploadInfo)
          // console.log(estimateSpeed)
          return uploadInfo;
      } catch (e) {
          e.size = size;
          e.duration = Date.now() - start;
          throw e;
      } finally {
          if (![WebSocket.CLOSED, WebSocket.CLOSING].includes(ws.readyState)) {
              ws.close();
          }
      }
  }
  
  export function uploadWs(
      encrypted,
      metadata,
      verifierB64,
      timeLimit,
      dlimit,
      pwd,
      token,
      onprogress
  ) {
      const canceller = {
          cancelled: false
      };
  
      return {
          cancel: function() {
              canceller.cancelled = true;
          },
  
          result: upload(
              encrypted,
              metadata,
              verifierB64,
              timeLimit,
              dlimit,
              pwd,
              token,
              onprogress,
              canceller
          )
      };
  }
  
  ////////////////////////
  
  async function downloadS(id, keychain, token, signal) {
      const auth = await keychain.authHeader();
  
      const response = await fetch(getApiUrl(`/api/download/${id}`), {
          signal: signal,
          method: 'GET',
          headers: {
              "x-token": token,
              Authorization: auth
          }
      });
  
      const authHeader = response.headers.get('WWW-Authenticate');
      if (authHeader) {
          keychain.nonce = parseNonce(authHeader);
      }
  
      if (response.status !== 200) {
          throw new Error(response.status);
      }
  
      return response.body;
  }
  
  async function tryDownloadStream(id, keychain, token, signal, tries = 2) {
      try {
          const result = await downloadS(id, keychain, token, signal);
          return result;
      } catch (e) {
          if (e.message === '401' && --tries > 0) {
              return tryDownloadStream(id, keychain, token, signal, tries);
          }
          if (e.name === 'AbortError') {
              throw new Error('0');
          }
          throw e;
      }
  }
  
  export function downloadStream(id, keychain, token) {
      const controller = new AbortController();
  
      function cancel() {
          controller.abort();
      }
      return {
          cancel,
          result: tryDownloadStream(id, keychain, token, controller.signal)
      };
  }
  
  //////////////////
  
  async function download(id, keychain, token, onprogress, canceller) {
      const auth = await keychain.authHeader();
      const xhr = new XMLHttpRequest();
      canceller.oncancel = function() {
          xhr.abort();
      };
      return new Promise(function(resolve, reject) {
          xhr.addEventListener('loadend', function() {
              canceller.oncancel = function() {};
              const authHeader = xhr.getResponseHeader('WWW-Authenticate');
              if (authHeader) {
                  keychain.nonce = parseNonce(authHeader);
              }
              if (xhr.status !== 200) {
                  return reject(new Error(xhr.status));
              }
  
              const blob = new Blob([xhr.response]);
              resolve(blob);
          });
  
          xhr.addEventListener('progress', function(event) {
              if (event.target.status === 200) {
                  onprogress(event.loaded);
              }
          });
          xhr.open('get', getApiUrl(`/api/download/blob/${id}`));
          xhr.setRequestHeader('Authorization', auth);
          xhr.setRequestHeader('x-token', token);
          xhr.responseType = 'blob';
          xhr.send();
          onprogress(0);
      });
  }
  
  async function tryDownload(id, keychain, token, onprogress, canceller, tries = 2) {
      try {
          const result = await download(id, keychain, token, onprogress, canceller);
          return result;
      } catch (e) {
          if (e.message === '401' && --tries > 0) {
              return tryDownload(id, keychain, token, onprogress, canceller, tries);
          }
          throw e;
      }
  }
  
  export function downloadFile(id, keychain, token, onprogress) {
      const canceller = {
          oncancel: function() {} // download() sets this
      };
  
      function cancel() {
          canceller.oncancel();
      }
      return {
          cancel,
          result: tryDownload(id, keychain, token, onprogress, canceller)
      };
  }
  
  export async function getFileList(bearerToken, kid) {
      const headers = new Headers({
          Authorization: `Bearer ${bearerToken}`
      });
      const response = await fetch(getApiUrl(`/api/filelist/${kid}`), {
          headers
      });
      if (response.ok) {
          const encrypted = await response.blob();
          return encrypted;
      }
      throw new Error(response.status);
  }
  
  export async function setFileList(bearerToken, kid, data) {
      const headers = new Headers({
          Authorization: `Bearer ${bearerToken}`
      });
      const response = await fetch(getApiUrl(`/api/filelist/${kid}`), {
          headers,
          method: 'POST',
          body: data
      });
      return response.ok;
  }
  
  export async function getConstants() {
      const response = await fetch(getApiUrl('/config'));
  
      if (response.ok) {
          const obj = await response.json();
          return obj;
      }
  
      throw new Error(response.status);
  }