const normalizeLine = line => {
  if (line.startsWith('Error: ')) {
    return line.slice('Error: '.length);
  }
  if (line.startsWith('VError: ')) {
    return line.slice('VError: '.length);
  }
  return line;
};
const getCombinedMessage = (error, message) => {
  const stringifiedError = normalizeLine(`${error}`);
  if (message) {
    return `${message}: ${stringifiedError}`;
  }
  return stringifiedError;
};
const NewLine$2 = '\n';
const getNewLineIndex$1 = (string, startIndex = undefined) => {
  return string.indexOf(NewLine$2, startIndex);
};
const mergeStacks = (parent, child) => {
  if (!child) {
    return parent;
  }
  const parentNewLineIndex = getNewLineIndex$1(parent);
  const childNewLineIndex = getNewLineIndex$1(child);
  if (childNewLineIndex === -1) {
    return parent;
  }
  const parentFirstLine = parent.slice(0, parentNewLineIndex);
  const childRest = child.slice(childNewLineIndex);
  const childFirstLine = normalizeLine(child.slice(0, childNewLineIndex));
  if (parentFirstLine.includes(childFirstLine)) {
    return parentFirstLine + childRest;
  }
  return child;
};
class VError extends Error {
  constructor(error, message) {
    const combinedMessage = getCombinedMessage(error, message);
    super(combinedMessage);
    this.name = 'VError';
    if (error instanceof Error) {
      this.stack = mergeStacks(this.stack, error.stack);
    }
    if (error.codeFrame) {
      // @ts-ignore
      this.codeFrame = error.codeFrame;
    }
    if (error.code) {
      // @ts-ignore
      this.code = error.code;
    }
  }
}

class AssertionError extends Error {
  constructor(message) {
    super(message);
    this.name = 'AssertionError';
  }
}
const Object$1 = 1;
const Number$1 = 2;
const Array$1 = 3;
const String = 4;
const Boolean = 5;
const Function = 6;
const Null = 7;
const Unknown = 8;
const getType = value => {
  switch (typeof value) {
    case 'number':
      return Number$1;
    case 'function':
      return Function;
    case 'string':
      return String;
    case 'object':
      if (value === null) {
        return Null;
      }
      if (Array.isArray(value)) {
        return Array$1;
      }
      return Object$1;
    case 'boolean':
      return Boolean;
    default:
      return Unknown;
  }
};
const object = value => {
  const type = getType(value);
  if (type !== Object$1) {
    throw new AssertionError('expected value to be of type object');
  }
};
const number = value => {
  const type = getType(value);
  if (type !== Number$1) {
    throw new AssertionError('expected value to be of type number');
  }
};
const string = value => {
  const type = getType(value);
  if (type !== String) {
    throw new AssertionError('expected value to be of type string');
  }
};

const isMessagePort = value => {
  return value && value instanceof MessagePort;
};
const isMessagePortMain = value => {
  return value && value.constructor && value.constructor.name === 'MessagePortMain';
};
const isOffscreenCanvas = value => {
  return typeof OffscreenCanvas !== 'undefined' && value instanceof OffscreenCanvas;
};
const isInstanceOf = (value, constructorName) => {
  return value?.constructor?.name === constructorName;
};
const isSocket = value => {
  return isInstanceOf(value, 'Socket');
};
const transferrables = [isMessagePort, isMessagePortMain, isOffscreenCanvas, isSocket];
const isTransferrable = value => {
  for (const fn of transferrables) {
    if (fn(value)) {
      return true;
    }
  }
  return false;
};
const walkValue = (value, transferrables, isTransferrable) => {
  if (!value) {
    return;
  }
  if (isTransferrable(value)) {
    transferrables.push(value);
    return;
  }
  if (Array.isArray(value)) {
    for (const item of value) {
      walkValue(item, transferrables, isTransferrable);
    }
    return;
  }
  if (typeof value === 'object') {
    for (const property of Object.values(value)) {
      walkValue(property, transferrables, isTransferrable);
    }
    return;
  }
};
const getTransferrables = value => {
  const transferrables = [];
  walkValue(value, transferrables, isTransferrable);
  return transferrables;
};
const attachEvents = that => {
  const handleMessage = (...args) => {
    const data = that.getData(...args);
    that.dispatchEvent(new MessageEvent('message', {
      data
    }));
  };
  that.onMessage(handleMessage);
  const handleClose = event => {
    that.dispatchEvent(new Event('close'));
  };
  that.onClose(handleClose);
};
class Ipc extends EventTarget {
  constructor(rawIpc) {
    super();
    this._rawIpc = rawIpc;
    attachEvents(this);
  }
}
const E_INCOMPATIBLE_NATIVE_MODULE = 'E_INCOMPATIBLE_NATIVE_MODULE';
const E_MODULES_NOT_SUPPORTED_IN_ELECTRON = 'E_MODULES_NOT_SUPPORTED_IN_ELECTRON';
const ERR_MODULE_NOT_FOUND = 'ERR_MODULE_NOT_FOUND';
const NewLine$1 = '\n';
const joinLines$1 = lines => {
  return lines.join(NewLine$1);
};
const RE_AT = /^\s+at/;
const RE_AT_PROMISE_INDEX = /^\s*at async Promise.all \(index \d+\)$/;
const isNormalStackLine = line => {
  return RE_AT.test(line) && !RE_AT_PROMISE_INDEX.test(line);
};
const getDetails = lines => {
  const index = lines.findIndex(isNormalStackLine);
  if (index === -1) {
    return {
      actualMessage: joinLines$1(lines),
      rest: []
    };
  }
  let lastIndex = index - 1;
  while (++lastIndex < lines.length) {
    if (!isNormalStackLine(lines[lastIndex])) {
      break;
    }
  }
  return {
    actualMessage: lines[index - 1],
    rest: lines.slice(index, lastIndex)
  };
};
const splitLines$1 = lines => {
  return lines.split(NewLine$1);
};
const RE_MESSAGE_CODE_BLOCK_START = /^Error: The module '.*'$/;
const RE_MESSAGE_CODE_BLOCK_END = /^\s* at/;
const isMessageCodeBlockStartIndex = line => {
  return RE_MESSAGE_CODE_BLOCK_START.test(line);
};
const isMessageCodeBlockEndIndex = line => {
  return RE_MESSAGE_CODE_BLOCK_END.test(line);
};
const getMessageCodeBlock = stderr => {
  const lines = splitLines$1(stderr);
  const startIndex = lines.findIndex(isMessageCodeBlockStartIndex);
  const endIndex = startIndex + lines.slice(startIndex).findIndex(isMessageCodeBlockEndIndex, startIndex);
  const relevantLines = lines.slice(startIndex, endIndex);
  const relevantMessage = relevantLines.join(' ').slice('Error: '.length);
  return relevantMessage;
};
const isModuleNotFoundMessage = line => {
  return line.includes('[ERR_MODULE_NOT_FOUND]');
};
const getModuleNotFoundError = stderr => {
  const lines = splitLines$1(stderr);
  const messageIndex = lines.findIndex(isModuleNotFoundMessage);
  const message = lines[messageIndex];
  return {
    code: ERR_MODULE_NOT_FOUND,
    message
  };
};
const isModuleNotFoundError = stderr => {
  if (!stderr) {
    return false;
  }
  return stderr.includes('ERR_MODULE_NOT_FOUND');
};
const isModulesSyntaxError = stderr => {
  if (!stderr) {
    return false;
  }
  return stderr.includes('SyntaxError: Cannot use import statement outside a module');
};
const RE_NATIVE_MODULE_ERROR = /^innerError Error: Cannot find module '.*.node'/;
const RE_NATIVE_MODULE_ERROR_2 = /was compiled against a different Node.js version/;
const isUnhelpfulNativeModuleError = stderr => {
  return RE_NATIVE_MODULE_ERROR.test(stderr) && RE_NATIVE_MODULE_ERROR_2.test(stderr);
};
const getNativeModuleErrorMessage = stderr => {
  const message = getMessageCodeBlock(stderr);
  return {
    code: E_INCOMPATIBLE_NATIVE_MODULE,
    message: `Incompatible native node module: ${message}`
  };
};
const getModuleSyntaxError = () => {
  return {
    code: E_MODULES_NOT_SUPPORTED_IN_ELECTRON,
    message: `ES Modules are not supported in electron`
  };
};
const getHelpfulChildProcessError = (stdout, stderr) => {
  if (isUnhelpfulNativeModuleError(stderr)) {
    return getNativeModuleErrorMessage(stderr);
  }
  if (isModulesSyntaxError(stderr)) {
    return getModuleSyntaxError();
  }
  if (isModuleNotFoundError(stderr)) {
    return getModuleNotFoundError(stderr);
  }
  const lines = splitLines$1(stderr);
  const {
    actualMessage,
    rest
  } = getDetails(lines);
  return {
    code: '',
    message: actualMessage,
    stack: rest
  };
};
class IpcError extends VError {
  // @ts-ignore
  constructor(betterMessage, stdout = '', stderr = '') {
    if (stdout || stderr) {
      // @ts-ignore
      const {
        code,
        message,
        stack
      } = getHelpfulChildProcessError(stdout, stderr);
      const cause = new Error(message);
      // @ts-ignore
      cause.code = code;
      cause.stack = stack;
      super(cause, betterMessage);
    } else {
      super(betterMessage);
    }
    // @ts-ignore
    this.name = 'IpcError';
    // @ts-ignore
    this.stdout = stdout;
    // @ts-ignore
    this.stderr = stderr;
  }
}
const readyMessage = 'ready';
const getData$2 = event => {
  return event.data;
};
const listen$7 = () => {
  // @ts-ignore
  if (typeof WorkerGlobalScope === 'undefined') {
    throw new TypeError('module is not in web worker scope');
  }
  return globalThis;
};
const signal$8 = global => {
  global.postMessage(readyMessage);
};
class IpcChildWithModuleWorker extends Ipc {
  getData(event) {
    return getData$2(event);
  }
  send(message) {
    // @ts-ignore
    this._rawIpc.postMessage(message);
  }
  sendAndTransfer(message) {
    const transfer = getTransferrables(message);
    // @ts-ignore
    this._rawIpc.postMessage(message, transfer);
  }
  dispose() {
    // ignore
  }
  onClose(callback) {
    // ignore
  }
  onMessage(callback) {
    this._rawIpc.addEventListener('message', callback);
  }
}
const wrap$f = global => {
  return new IpcChildWithModuleWorker(global);
};
const waitForFirstMessage = async port => {
  const {
    promise,
    resolve
  } = Promise.withResolvers();
  port.addEventListener('message', resolve, {
    once: true
  });
  const event = await promise;
  // @ts-ignore
  return event.data;
};
const listen$6 = async () => {
  const parentIpcRaw = listen$7();
  signal$8(parentIpcRaw);
  const parentIpc = wrap$f(parentIpcRaw);
  const firstMessage = await waitForFirstMessage(parentIpc);
  if (firstMessage.method !== 'initialize') {
    throw new IpcError('unexpected first message');
  }
  const type = firstMessage.params[0];
  if (type === 'message-port') {
    parentIpc.send({
      id: firstMessage.id,
      jsonrpc: '2.0',
      result: null
    });
    parentIpc.dispose();
    const port = firstMessage.params[1];
    return port;
  }
  return globalThis;
};
class IpcChildWithModuleWorkerAndMessagePort extends Ipc {
  getData(event) {
    return getData$2(event);
  }
  send(message) {
    this._rawIpc.postMessage(message);
  }
  sendAndTransfer(message) {
    const transfer = getTransferrables(message);
    this._rawIpc.postMessage(message, transfer);
  }
  dispose() {
    if (this._rawIpc.close) {
      this._rawIpc.close();
    }
  }
  onClose(callback) {
    // ignore
  }
  onMessage(callback) {
    this._rawIpc.addEventListener('message', callback);
    this._rawIpc.start();
  }
}
const wrap$e = port => {
  return new IpcChildWithModuleWorkerAndMessagePort(port);
};
const IpcChildWithModuleWorkerAndMessagePort$1 = {
  __proto__: null,
  listen: listen$6,
  wrap: wrap$e
};
const addListener = (emitter, type, callback) => {
  if ('addEventListener' in emitter) {
    emitter.addEventListener(type, callback);
  } else {
    emitter.on(type, callback);
  }
};
const removeListener = (emitter, type, callback) => {
  if ('removeEventListener' in emitter) {
    emitter.removeEventListener(type, callback);
  } else {
    emitter.off(type, callback);
  }
};
const getFirstEvent = (eventEmitter, eventMap) => {
  const {
    promise,
    resolve
  } = Promise.withResolvers();
  const listenerMap = Object.create(null);
  const cleanup = value => {
    for (const event of Object.keys(eventMap)) {
      removeListener(eventEmitter, event, listenerMap[event]);
    }
    resolve(value);
  };
  for (const [event, type] of Object.entries(eventMap)) {
    const listener = event => {
      cleanup({
        event,
        type
      });
    };
    addListener(eventEmitter, event, listener);
    listenerMap[event] = listener;
  }
  return promise;
};
const Message$1 = 3;
const create$5$1 = async ({
  isMessagePortOpen,
  messagePort
}) => {
  if (!isMessagePort(messagePort)) {
    throw new IpcError('port must be of type MessagePort');
  }
  if (isMessagePortOpen) {
    return messagePort;
  }
  const eventPromise = getFirstEvent(messagePort, {
    message: Message$1
  });
  messagePort.start();
  const {
    event,
    type
  } = await eventPromise;
  if (type !== Message$1) {
    throw new IpcError('Failed to wait for ipc message');
  }
  if (event.data !== readyMessage) {
    throw new IpcError('unexpected first message');
  }
  return messagePort;
};
const signal$1 = messagePort => {
  messagePort.start();
};
class IpcParentWithMessagePort extends Ipc {
  getData = getData$2;
  send(message) {
    this._rawIpc.postMessage(message);
  }
  sendAndTransfer(message) {
    const transfer = getTransferrables(message);
    this._rawIpc.postMessage(message, transfer);
  }
  dispose() {
    this._rawIpc.close();
  }
  onMessage(callback) {
    this._rawIpc.addEventListener('message', callback);
  }
  onClose(callback) {}
}
const wrap$5 = messagePort => {
  return new IpcParentWithMessagePort(messagePort);
};
const IpcParentWithMessagePort$1 = {
  __proto__: null,
  create: create$5$1,
  signal: signal$1,
  wrap: wrap$5
};

const Two$1 = '2.0';
const callbacks = Object.create(null);
const get$3 = id => {
  return callbacks[id];
};
const remove$2 = id => {
  delete callbacks[id];
};
class JsonRpcError extends Error {
  constructor(message) {
    super(message);
    this.name = 'JsonRpcError';
  }
}
const NewLine = '\n';
const DomException = 'DOMException';
const ReferenceError$1 = 'ReferenceError';
const SyntaxError$1 = 'SyntaxError';
const TypeError$1 = 'TypeError';
const getErrorConstructor = (message, type) => {
  if (type) {
    switch (type) {
      case DomException:
        return DOMException;
      case TypeError$1:
        return TypeError;
      case SyntaxError$1:
        return SyntaxError;
      case ReferenceError$1:
        return ReferenceError;
      default:
        return Error;
    }
  }
  if (message.startsWith('TypeError: ')) {
    return TypeError;
  }
  if (message.startsWith('SyntaxError: ')) {
    return SyntaxError;
  }
  if (message.startsWith('ReferenceError: ')) {
    return ReferenceError;
  }
  return Error;
};
const constructError = (message, type, name) => {
  const ErrorConstructor = getErrorConstructor(message, type);
  if (ErrorConstructor === DOMException && name) {
    return new ErrorConstructor(message, name);
  }
  if (ErrorConstructor === Error) {
    const error = new Error(message);
    if (name && name !== 'VError') {
      error.name = name;
    }
    return error;
  }
  return new ErrorConstructor(message);
};
const joinLines = lines => {
  return lines.join(NewLine);
};
const splitLines = lines => {
  return lines.split(NewLine);
};
const getCurrentStack = () => {
  const stackLinesToSkip = 3;
  const currentStack = joinLines(splitLines(new Error().stack || '').slice(stackLinesToSkip));
  return currentStack;
};
const getNewLineIndex = (string, startIndex = undefined) => {
  return string.indexOf(NewLine, startIndex);
};
const getParentStack = error => {
  let parentStack = error.stack || error.data || error.message || '';
  if (parentStack.startsWith('    at')) {
    parentStack = error.message + NewLine + parentStack;
  }
  return parentStack;
};
const MethodNotFound = -32601;
const Custom = -32001;
const restoreJsonRpcError = error => {
  const currentStack = getCurrentStack();
  if (error && error instanceof Error) {
    if (typeof error.stack === 'string') {
      error.stack = error.stack + NewLine + currentStack;
    }
    return error;
  }
  if (error && error.code && error.code === MethodNotFound) {
    const restoredError = new JsonRpcError(error.message);
    const parentStack = getParentStack(error);
    restoredError.stack = parentStack + NewLine + currentStack;
    return restoredError;
  }
  if (error && error.message) {
    const restoredError = constructError(error.message, error.type, error.name);
    if (error.data) {
      if (error.data.stack && error.data.type && error.message) {
        restoredError.stack = error.data.type + ': ' + error.message + NewLine + error.data.stack + NewLine + currentStack;
      } else if (error.data.stack) {
        restoredError.stack = error.data.stack;
      }
      if (error.data.codeFrame) {
        // @ts-ignore
        restoredError.codeFrame = error.data.codeFrame;
      }
      if (error.data.code) {
        // @ts-ignore
        restoredError.code = error.data.code;
      }
      if (error.data.type) {
        // @ts-ignore
        restoredError.name = error.data.type;
      }
    } else {
      if (error.stack) {
        const lowerStack = restoredError.stack || '';
        // @ts-ignore
        const indexNewLine = getNewLineIndex(lowerStack);
        const parentStack = getParentStack(error);
        // @ts-ignore
        restoredError.stack = parentStack + lowerStack.slice(indexNewLine);
      }
      if (error.codeFrame) {
        // @ts-ignore
        restoredError.codeFrame = error.codeFrame;
      }
    }
    return restoredError;
  }
  if (typeof error === 'string') {
    return new Error(`JsonRpc Error: ${error}`);
  }
  return new Error(`JsonRpc Error: ${error}`);
};
const unwrapJsonRpcResult = responseMessage => {
  if ('error' in responseMessage) {
    const restoredError = restoreJsonRpcError(responseMessage.error);
    throw restoredError;
  }
  if ('result' in responseMessage) {
    return responseMessage.result;
  }
  throw new JsonRpcError('unexpected response message');
};
const warn = (...args) => {
  console.warn(...args);
};
const resolve = (id, response) => {
  const fn = get$3(id);
  if (!fn) {
    console.log(response);
    warn(`callback ${id} may already be disposed`);
    return;
  }
  fn(response);
  remove$2(id);
};
const E_COMMAND_NOT_FOUND = 'E_COMMAND_NOT_FOUND';
const getErrorType = prettyError => {
  if (prettyError && prettyError.type) {
    return prettyError.type;
  }
  if (prettyError && prettyError.constructor && prettyError.constructor.name) {
    return prettyError.constructor.name;
  }
  return undefined;
};
const isAlreadyStack = line => {
  return line.trim().startsWith('at ');
};
const getStack = prettyError => {
  const stackString = prettyError.stack || '';
  const newLineIndex = stackString.indexOf('\n');
  if (newLineIndex !== -1 && !isAlreadyStack(stackString.slice(0, newLineIndex))) {
    return stackString.slice(newLineIndex + 1);
  }
  return stackString;
};
const getErrorProperty = (error, prettyError) => {
  if (error && error.code === E_COMMAND_NOT_FOUND) {
    return {
      code: MethodNotFound,
      message: error.message,
      data: error.stack
    };
  }
  return {
    code: Custom,
    message: prettyError.message,
    data: {
      stack: getStack(prettyError),
      codeFrame: prettyError.codeFrame,
      type: getErrorType(prettyError),
      code: prettyError.code,
      name: prettyError.name
    }
  };
};
const create$1$1 = (id, error) => {
  return {
    jsonrpc: Two$1,
    id,
    error
  };
};
const getErrorResponse = (id, error, preparePrettyError, logError) => {
  const prettyError = preparePrettyError(error);
  logError(error, prettyError);
  const errorProperty = getErrorProperty(error, prettyError);
  return create$1$1(id, errorProperty);
};
const create$6 = (message, result) => {
  return {
    jsonrpc: Two$1,
    id: message.id,
    result: result ?? null
  };
};
const getSuccessResponse = (message, result) => {
  const resultProperty = result ?? null;
  return create$6(message, resultProperty);
};
const getErrorResponseSimple = (id, error) => {
  return {
    jsonrpc: Two$1,
    id,
    error: {
      code: Custom,
      // @ts-ignore
      message: error.message,
      data: error
    }
  };
};
const getResponse = async (message, ipc, execute, preparePrettyError, logError, requiresSocket) => {
  try {
    const result = requiresSocket(message.method) ? await execute(message.method, ipc, ...message.params) : await execute(message.method, ...message.params);
    return getSuccessResponse(message, result);
  } catch (error) {
    if (ipc.canUseSimpleErrorResponse) {
      return getErrorResponseSimple(message.id, error);
    }
    return getErrorResponse(message.id, error, preparePrettyError, logError);
  }
};
const defaultPreparePrettyError = error => {
  return error;
};
const defaultLogError = () => {
  // ignore
};
const defaultRequiresSocket = () => {
  return false;
};
const defaultResolve = resolve;

// TODO maybe remove this in v6 or v7, only accept options object to simplify the code
const normalizeParams = args => {
  if (args.length === 1) {
    const options = args[0];
    return {
      ipc: options.ipc,
      message: options.message,
      execute: options.execute,
      resolve: options.resolve || defaultResolve,
      preparePrettyError: options.preparePrettyError || defaultPreparePrettyError,
      logError: options.logError || defaultLogError,
      requiresSocket: options.requiresSocket || defaultRequiresSocket
    };
  }
  return {
    ipc: args[0],
    message: args[1],
    execute: args[2],
    resolve: args[3],
    preparePrettyError: args[4],
    logError: args[5],
    requiresSocket: args[6]
  };
};
const handleJsonRpcMessage = async (...args) => {
  const options = normalizeParams(args);
  const {
    message,
    ipc,
    execute,
    resolve,
    preparePrettyError,
    logError,
    requiresSocket
  } = options;
  if ('id' in message) {
    if ('method' in message) {
      const response = await getResponse(message, ipc, execute, preparePrettyError, logError, requiresSocket);
      try {
        ipc.send(response);
      } catch (error) {
        const errorResponse = getErrorResponse(message.id, error, preparePrettyError, logError);
        ipc.send(errorResponse);
      }
      return;
    }
    resolve(message.id, message);
    return;
  }
  if ('method' in message) {
    await getResponse(message, ipc, execute, preparePrettyError, logError, requiresSocket);
    return;
  }
  throw new JsonRpcError('unexpected message');
};

class CommandNotFoundError extends Error {
  constructor(command) {
    super(`Command not found ${command}`);
    this.name = 'CommandNotFoundError';
  }
}
const commands = Object.create(null);
const register$4 = commandMap => {
  Object.assign(commands, commandMap);
};
const getCommand = key => {
  return commands[key];
};
const execute = (command, ...args) => {
  const fn = getCommand(command);
  if (!fn) {
    throw new CommandNotFoundError(command);
  }
  return fn(...args);
};

const Two = '2.0';
const create$t = (method, params) => {
  return {
    jsonrpc: Two,
    method,
    params
  };
};
const create$s = (id, method, params) => {
  const message = {
    id,
    jsonrpc: Two,
    method,
    params
  };
  return message;
};
let id = 0;
const create$r = () => {
  return ++id;
};

/* eslint-disable n/no-unsupported-features/es-syntax */

const registerPromise = map => {
  const id = create$r();
  const {
    promise,
    resolve
  } = Promise.withResolvers();
  map[id] = resolve;
  return {
    id,
    promise
  };
};

// @ts-ignore
const invokeHelper = async (callbacks, ipc, method, params, useSendAndTransfer) => {
  const {
    id,
    promise
  } = registerPromise(callbacks);
  const message = create$s(id, method, params);
  if (useSendAndTransfer && ipc.sendAndTransfer) {
    ipc.sendAndTransfer(message);
  } else {
    ipc.send(message);
  }
  const responseMessage = await promise;
  return unwrapJsonRpcResult(responseMessage);
};
const createRpc = ipc => {
  const callbacks = Object.create(null);
  ipc._resolve = (id, response) => {
    const fn = callbacks[id];
    if (!fn) {
      console.warn(`callback ${id} may already be disposed`);
      return;
    }
    fn(response);
    delete callbacks[id];
  };
  const rpc = {
    async dispose() {
      await ipc?.dispose();
    },
    invoke(method, ...params) {
      return invokeHelper(callbacks, ipc, method, params, false);
    },
    invokeAndTransfer(method, ...params) {
      return invokeHelper(callbacks, ipc, method, params, true);
    },
    // @ts-ignore
    ipc,
    /**
     * @deprecated
     */
    send(method, ...params) {
      const message = create$t(method, params);
      ipc.send(message);
    }
  };
  return rpc;
};
const requiresSocket = () => {
  return false;
};
const preparePrettyError = error => {
  return error;
};
const logError = () => {
  // handled by renderer worker
};
const handleMessage = event => {
  const actualRequiresSocket = event?.target?.requiresSocket || requiresSocket;
  const actualExecute = event?.target?.execute || execute;
  return handleJsonRpcMessage(event.target, event.data, actualExecute, event.target._resolve, preparePrettyError, logError, actualRequiresSocket);
};
const handleIpc = ipc => {
  if ('addEventListener' in ipc) {
    ipc.addEventListener('message', handleMessage);
  } else if ('on' in ipc) {
    // deprecated
    ipc.on('message', handleMessage);
  }
};
const listen$1 = async (module, options) => {
  const rawIpc = await module.listen(options);
  if (module.signal) {
    module.signal(rawIpc);
  }
  const ipc = module.wrap(rawIpc);
  return ipc;
};
const create$e = async ({
  commandMap,
  isMessagePortOpen,
  messagePort
}) => {
  // TODO create a commandMap per rpc instance
  register$4(commandMap);
  const rawIpc = await IpcParentWithMessagePort$1.create({
    isMessagePortOpen,
    messagePort
  });
  const ipc = IpcParentWithMessagePort$1.wrap(rawIpc);
  handleIpc(ipc);
  const rpc = createRpc(ipc);
  return rpc;
};
const MessagePortRpcParent = {
  __proto__: null,
  create: create$e
};
const create$5 = async ({
  commandMap,
  isMessagePortOpen = true,
  messagePort
}) => {
  // TODO create a commandMap per rpc instance
  register$4(commandMap);
  const rawIpc = await IpcParentWithMessagePort$1.create({
    isMessagePortOpen,
    messagePort
  });
  const ipc = IpcParentWithMessagePort$1.wrap(rawIpc);
  handleIpc(ipc);
  const rpc = createRpc(ipc);
  messagePort.start();
  return rpc;
};
const create$4 = async ({
  commandMap,
  messagePort
}) => {
  return create$5({
    commandMap,
    messagePort
  });
};
const PlainMessagePortRpcParent = {
  __proto__: null,
  create: create$4
};
const create$3$1 = async ({
  commandMap,
  isMessagePortOpen,
  send
}) => {
  const {
    port1,
    port2
  } = new MessageChannel();
  await send(port1);
  return create$5({
    commandMap,
    isMessagePortOpen,
    messagePort: port2
  });
};
const TransferMessagePortRpcParent = {
  __proto__: null,
  create: create$3$1
};
const create$2$1 = async ({
  commandMap
}) => {
  // TODO create a commandMap per rpc instance
  register$4(commandMap);
  const ipc = await listen$1(IpcChildWithModuleWorkerAndMessagePort$1);
  handleIpc(ipc);
  const rpc = createRpc(ipc);
  return rpc;
};
const WebWorkerRpcClient = {
  __proto__: null,
  create: create$2$1
};
const createMockRpc = ({
  commandMap
}) => {
  const invocations = [];
  const invoke = (method, ...params) => {
    invocations.push([method, ...params]);
    const command = commandMap[method];
    if (!command) {
      throw new Error(`command ${method} not found`);
    }
    return command(...params);
  };
  const mockRpc = {
    invocations,
    invoke,
    invokeAndTransfer: invoke
  };
  return mockRpc;
};

const Div = 4;

const ExtensionHostWorker = 44;
const RendererWorker$1 = 1;

const SetDom2 = 'Viewlet.setDom2';

const rpcs$1 = Object.create(null);
const set$4 = (id, rpc) => {
  rpcs$1[id] = rpc;
};
const get$2 = id => {
  return rpcs$1[id];
};
const remove$1 = id => {
  delete rpcs$1[id];
};

/* eslint-disable  @typescript-eslint/explicit-function-return-type */
const create$3 = rpcId => {
  return {
    async dispose() {
      const rpc = get$2(rpcId);
      await rpc.dispose();
    },
    // @ts-ignore
    invoke(method, ...params) {
      const rpc = get$2(rpcId);
      // @ts-ignore
      return rpc.invoke(method, ...params);
    },
    // @ts-ignore
    invokeAndTransfer(method, ...params) {
      const rpc = get$2(rpcId);
      // @ts-ignore
      return rpc.invokeAndTransfer(method, ...params);
    },
    registerMockRpc(commandMap) {
      const mockRpc = createMockRpc({
        commandMap
      });
      set$4(rpcId, mockRpc);
      // @ts-ignore
      mockRpc[Symbol.dispose] = () => {
        remove$1(rpcId);
      };
      // @ts-ignore
      return mockRpc;
    },
    set(rpc) {
      set$4(rpcId, rpc);
    }
  };
};

const {
  invoke: invoke$4,
  invokeAndTransfer: invokeAndTransfer$2,
  set: set$3
} = create$3(ExtensionHostWorker);

const {
  invoke: invoke$3,
  invokeAndTransfer: invokeAndTransfer$1,
  set: set$2
} = create$3(RendererWorker$1);
const sendMessagePortToExtensionHostWorker = async (port, rpcId = 0) => {
  const command = 'HandleMessagePort.handleMessagePort2';
  await invokeAndTransfer$1('SendMessagePortToExtensionHostWorker.sendMessagePortToExtensionHostWorker', port, command, rpcId);
};

const RendererWorker = {
  __proto__: null,
  invoke: invoke$3,
  invokeAndTransfer: invokeAndTransfer$1,
  sendMessagePortToExtensionHostWorker,
  set: set$2
};

const getCredentialLess = locationHost => {
  if (locationHost.startsWith('localhost:')) {
    // disabled to improve performance and make testing easier
    return false;
  }
  return true;
};

const createUrl = (protocol, host) => {
  return protocol + '//' + host;
};

const createLocalHostUrl = (locationProtocol, locationHost, isGitpod, webViewPort, webViewId, useNewWebViewHandler) => {
  if (isGitpod) {
    return createUrl(locationProtocol, locationHost.replace('3000', `${webViewPort}`));
  }
  if (useNewWebViewHandler) {
    return `http://localhost:${webViewPort}/${webViewId}`;
  }
  return `http://localhost:${webViewPort}`;
};

const Web = 1;
const Electron = 2;
const Remote = 3;

const getIframeSrcRemoteBaseUrl = (webViewRoot, locationOrigin, platform, webViewId, useNewWebViewHandler, root) => {
  if (webViewRoot && (webViewRoot.startsWith('http:') || webViewRoot.startsWith('https:'))) {
    if (webViewRoot.startsWith(locationOrigin)) {
      const baseUrl = webViewRoot.slice(locationOrigin.length);
      if (baseUrl.startsWith('/remote')) {
        return baseUrl;
      }
      return `/remote${root}${baseUrl}`;
    }
    return webViewRoot;
  }
  if (platform === Remote && useNewWebViewHandler && webViewId) {
    return `/${webViewId}`;
  }
  return '';
};

const getWebView = (webViews, webViewId) => {
  for (const webView of webViews) {
    if (webView.id === webViewId) {
      return webView;
    }
  }
  return undefined;
};

const getWebViewHtml = (baseUrl, locationOrigin, elements, assetDir) => {
  if (!elements) {
    return '';
  }
  const middle = ['<meta charset="utf-8">'];
  for (const element of elements) {
    switch (element.type) {
      case 'css':
        middle.push(`<link rel="stylesheet" href="${locationOrigin}${baseUrl}/${element.path}" />`);
        break;
      case 'script':
        middle.push(`<script type="module" src="${locationOrigin}${assetDir}/js/preview-injected.js"></script>`);
        middle.push(`<script type="module" src="${locationOrigin}${baseUrl}/${element.path}"></script>`);
        break;
      case 'title':
        middle.push(`<title>${element.value}</title>`);
        break;
    }
  }
  const middleHtml = middle.join('\n    ');
  const html = `<!DOCTYPE html>
<html>
  <head>
    ${middleHtml}
  </head>
</html>
`;
  return html;
};

const getWebViewPath = (webViews, webViewId) => {
  const webView = getWebView(webViews, webViewId);
  if (!webView) {
    return '';
  }
  return webView.path;
};
const getWebViewUri = (webViews, webViewId) => {
  const webViewPath = getWebViewPath(webViews, webViewId);
  if (!webViewPath) {
    return '';
  }
  if (webViewPath.startsWith('/')) {
    // TODO make it work on windows also
    return `file://${webViewPath}`;
  }
  return webViewPath;
};

const getIframeSrcRemote = (webViews, webViewPort, webViewId, locationProtocol, locationOrigin, locationHost, isGitpod, root, platform, assetDir, webViewScheme, useNewWebViewHandler) => {
  const webView = getWebView(webViews, webViewId);
  const webViewUri = getWebViewUri(webViews, webViewId);
  if (!webViewUri) {
    return undefined;
  }
  if (!webView) {
    return undefined;
  }
  let iframeSrc = webViewUri;
  let webViewRoot = webViewUri;

  // TODO when running in remote, try scope webviews by path or if possible by domain
  if (platform === Electron) {
    const webViewId = webView.id;
    iframeSrc = `${webViewScheme}://${webViewId}`;
  } else if (platform === Remote) {
    webViewRoot = webView.uri;
    iframeSrc = createLocalHostUrl(locationProtocol, locationHost, isGitpod, webViewPort, webViewId, useNewWebViewHandler);
  }
  const baseUrl = getIframeSrcRemoteBaseUrl(webViewRoot, locationOrigin, platform, webViewId, useNewWebViewHandler, root);
  const iframeContent = getWebViewHtml(baseUrl, '', webView.elements, assetDir);
  // TODO either
  // - load webviews the same as in web using blob urls
  // - load webviews from a pattern like /webviews/:id/:fileName
  return {
    iframeContent,
    iframeSrc,
    webViewRoot
  };
};

const getBlobUrl = (content, contentType) => {
  const blob = new Blob([content], {
    type: contentType
  });
  const url = URL.createObjectURL(blob); // TODO dispose
  return url;
};

const getDefaultBaseUrl = webView => {
  const {
    path,
    remotePath
  } = webView;
  if (remotePath) {
    if (remotePath.endsWith('/index.html')) {
      return remotePath.slice(0, -'/index.html'.length);
    }
    return remotePath;
  }
  if (path) {
    if (path.endsWith('/index.html')) {
      return path.slice(0, -'/index.html'.length);
    }
    return path;
  }
  return '';
};

const getWebViewBaseUrl = (webView, locationOrigin) => {
  const defaultBaseUrl = getDefaultBaseUrl(webView);
  if (defaultBaseUrl.startsWith(locationOrigin)) {
    return defaultBaseUrl.slice(locationOrigin.length);
  }
  return defaultBaseUrl;
};

const getIframeSrc$1 = (webView, locationOrigin, assetDir) => {
  const baseUrl = getWebViewBaseUrl(webView, locationOrigin);
  const srcHtml = getWebViewHtml(baseUrl, locationOrigin, webView.elements, assetDir);
  if (srcHtml) {
    const blobUrl = getBlobUrl(srcHtml, 'text/html');
    return {
      iframeContent: '',
      iframeSrc: blobUrl,
      webViewRoot: ''
    };
  }
  return undefined;
};

const getIframeSrc = (webViews, webViewId, webViewPort, root, isGitpod, locationProtocol, locationHost, locationOrigin, platform, assetDir, webViewScheme, useNewWebViewHandler) => {
  try {
    const webView = getWebView(webViews, webViewId);
    if (platform === Web) {
      return getIframeSrc$1(webView, locationOrigin, assetDir);
    }
    return getIframeSrcRemote(webViews, webViewPort, webViewId, locationProtocol, locationOrigin, locationHost, isGitpod, root, platform, assetDir, webViewScheme, useNewWebViewHandler);
  } catch (error) {
    throw new VError(error, `Failed to construct webview iframe src`);
  }
};

const getPortTuple = () => {
  const {
    port1,
    port2
  } = new MessageChannel();
  return {
    port1,
    port2
  };
};

const {
  invoke: invoke$2} = RendererWorker;

const getSavedState = async () => {
  // @ts-ignore
  return invoke$2('WebView.getSavedState');
};

const getSavedWebViewState = async id => {
  const states = await getSavedState();
  if (!states) {
    return undefined;
  }
  if (!Array.isArray(states)) {
    return undefined;
  }
  for (const item of states) {
    if (item && item.key && item.key === id && item.value && item.value.state) {
      return item.value.state;
    }
    if (item && item.key && item.key === id && item.value) {
      return item.value;
    }
  }
  return undefined;
};

const SemiColon = ';';
const Space = ' ';

const addSemicolon = line => {
  return line + SemiColon;
};

const getContentSecurityPolicy = items => {
  return items.map(addSemicolon).join(Space);
};

const getWebViewCsp = webView => {
  if (webView && webView.contentSecurityPolicy) {
    return getContentSecurityPolicy(webView.contentSecurityPolicy);
  }
  const csp = getContentSecurityPolicy([`default-src 'none'`, `script-src 'self'`, `style-src 'self'`, `img-src 'self'`, `media-src 'self'`]);
  return csp;
};

const getWebViewFrameAncestors = (locationProtocol, locationHost) => {
  const frameAncestors = createUrl(locationProtocol, locationHost);
  return frameAncestors;
};

const getWebViewOrigin = (webViewPort, platform, webViewScheme, webViewId) => {
  switch (platform) {
    case Electron:
      return `${webViewScheme}://${webViewId}`;
    case Remote:
      // TODO support gitpod / codespaces
      return `http://localhost:${webViewPort}`;
    default:
      // TODO
      return '*';
  }
};

const CrossOriginIsolated = 'cross-origin-isolated';
const getIframePermissionPolicy = webView => {
  const extensionAllow = webView.allow || [];
  if (extensionAllow.includes(CrossOriginIsolated)) {
    return extensionAllow;
  }
  return [CrossOriginIsolated, ...extensionAllow];
};

const getWebViews = async () => {
  // @ts-ignore
  return invoke$3('WebView.getWebViews');
};

const AllowScripts = 'allow-scripts';
const AllowSameOrigin = 'allow-same-origin';

const getIframeSandbox = (webView, platform) => {
  const extensionSandbox = webView.sandbox || [];
  if (platform === Remote) {
    return [AllowScripts, AllowSameOrigin, ...extensionSandbox]; // TODO maybe disallow same origin
  }
  if (platform === Web) {
    return [AllowScripts, ...extensionSandbox];
  }
  return [AllowScripts, AllowSameOrigin, ...extensionSandbox];
};

const getWebViewTitle = webView => {
  if (webView && webView.elements && Array.isArray(webView.elements)) {
    for (const item of webView.elements) {
      if (item && item.type === 'title' && typeof item.value === 'string') {
        return item.value;
      }
    }
  }
  return 'WebView';
};

const state = {
  id: 0
};
const create$2 = () => {
  return ++state.id;
};

const getOrigin = () => {
  return location.origin;
};
const getHost = () => {
  return location.host;
};
const getProtocol$1 = () => {
  return location.protocol;
};
const getPort = () => {
  return location.port;
};

const invoke$1 = async (method, ...params) => {
  // @ts-ignore
  return invoke$3('WebView.compatRendererProcessInvoke', method, ...params);
};
const invokeAndTransfer = async (method, ...params) => {
  // @ts-ignore
  return invokeAndTransfer$1('WebView.compatRendererProcessInvokeAndTransfer', method, ...params);
};

const WebView = 'lvce-oss-webview';

const setPort = async (uid, port, origin, portType) => {
  await invokeAndTransfer('WebView.setPort', uid, port, origin, portType);
};

const invoke = async (method, ...params) => {
  // @ts-ignore
  return invoke$3('WebView.compatSharedProcessInvoke', method, ...params);
};

const registerProtocol = async () => {
  await invoke('WebViewServer.registerProtocol');
};
const create$1 = async (previewServerId, useNewWebViewHandler) => {
  await invoke('WebViewServer.create', previewServerId, useNewWebViewHandler);
};
const start = async (previewServerId, webViewPort) => {
  await invoke('WebViewServer.start', previewServerId, webViewPort);
};
const setInfo2 = async info => {
  await invoke('WebViewServer.setInfo2', info);
};
const setInfo = async (previewServerId, webViewId, webViewRoot, contentSecurityPolicy, iframeContent) => {
  await invoke('WebViewServer.setInfo', previewServerId, webViewId, webViewRoot, contentSecurityPolicy, iframeContent);
};
const setHandler = async (previewServerId, frameAncestors, webViewRoot, contentSecurityPolicy, iframeContent, remotePathPrefix) => {
  await invoke('WebViewServer.setHandler', previewServerId, frameAncestors, webViewRoot, contentSecurityPolicy, iframeContent);
};

const register$3 = async (previewServerId, webViewPort, frameAncestors, webViewRoot, csp, iframeContent, webViewId, remotePathPrefix, useNewWebViewHandler) => {
  await registerProtocol();
  await create$1(previewServerId, useNewWebViewHandler); // TODO move this up

  // TODO send info to electron which domain maps to which webview root.
  // for example, video-preview maps to domain lvce-oss-webview://video-preview
  // and webview root  /home/user/lvce/extensions/video-preview
  // Then the request handler can decide by domain which webview root to use.
  // Another option would be in electron to check what iframe (webContents)
  // responds to which webviewRoot and setting a webviewRoot per webContents instance
  await setInfo(previewServerId, webViewId, webViewRoot, csp, iframeContent);
};

const register$2 = async (previewServerId, webViewPort, frameAncestors, webViewRoot, csp, iframeContent, webViewId, remotePathPrefix, useNewWebViewHandler) => {
  // TODO apply something similar for electron
  // TODO pass webview root, so that only these resources can be accessed
  // TODO pass csp configuration to server
  // TODO pass coop / coep configuration to server
  await create$1(previewServerId, useNewWebViewHandler); // TODO move this up
  await start(previewServerId, webViewPort); // TODO move this up
  await (useNewWebViewHandler ? setInfo2({
    contentSecurityPolicy: csp,
    iframeContent,
    remotePathPrefix,
    webViewId,
    webViewRoot
  }) : setHandler(previewServerId, frameAncestors, webViewRoot, csp, iframeContent));
  // TODO make this work in gitpod also
};

const register$1 = async () => {
  // noop
};

const getModule = platform => {
  switch (platform) {
    case Electron:
      return register$3;
    case Remote:
      return register$2;
    default:
      return register$1;
  }
};

const register = async (previewServerId, webViewPort, frameAncestors, webViewRoot, csp, iframeContent, platform, webViewId, remotePathPrefix, useNewWebViewHandler) => {
  const fn = getModule(platform);
  return fn(previewServerId, webViewPort, frameAncestors, webViewRoot, csp, iframeContent, webViewId, remotePathPrefix, useNewWebViewHandler);
};

const create2 = async ({
  assetDir = '',
  id,
  isGitpod,
  platform,
  previewServerId,
  uri,
  webViewId,
  webViewPort,
  webViewScheme = WebView
}) => {
  let root = '';
  if (platform === Remote) {
    root = await invoke('Platform.getRoot');
  }
  const webViews = await getWebViews();
  const locationProtocol = getProtocol$1();
  const locationHost = getHost();
  const locationOrigin = getOrigin();
  const iframeResult = getIframeSrc(webViews, webViewId, webViewPort, root, isGitpod, locationProtocol, locationHost, locationOrigin, platform, assetDir, webViewScheme, false);
  if (!iframeResult) {
    return undefined;
  }
  const webView = getWebView(webViews, webViewId);

  // TODO move all of this to iframe worker
  const {
    iframeContent,
    iframeSrc,
    webViewRoot
  } = iframeResult;
  const frameAncestors = getWebViewFrameAncestors(locationProtocol, locationHost);
  const frameTitle = getWebViewTitle(webView);
  // TODO figure out order for events, e.g.
  // 1. activate extension, create webview and ports in parallel
  // 2. wait for webview to load (?)
  // 3. setup extension host worker rpc
  // 4. create webview in extension host worker and load content

  const csp = getWebViewCsp(webView);
  const sandbox = getIframeSandbox(webView, platform);
  const permissionPolicy = getIframePermissionPolicy(webView);
  const permissionPolicyString = permissionPolicy.join('; ');
  const iframeCsp = platform === Web ? csp : '';
  const credentialless = getCredentialLess(locationHost);
  await invoke$2('ExtensionHostManagement.activateByEvent', `onWebView:${webViewId}`);
  const {
    port1,
    port2
  } = getPortTuple();
  const portId = create$2();
  const remotePathPrefix = '/remote';
  await register(previewServerId, webViewPort, frameAncestors, webViewRoot, csp, iframeContent, platform, webViewId, remotePathPrefix);
  await invoke$1('WebView.create', id, iframeSrc, sandbox, iframeCsp, credentialless, permissionPolicyString, frameTitle);

  // TODO maybe iframe waitForLoad is not needed. since it cannot be used detect errors anyway
  // and causes flash of unstyled content, maybe a better way would be to just send the
  // port and wait for the first port message
  await invoke$1('WebView.load', id);
  const origin = getWebViewOrigin(webViewPort, platform, webViewScheme, webViewId);
  const portType = '';
  await setPort(id, port1, origin, portType);
  await invokeAndTransfer$2('ExtensionHostWebView.create', webViewId, port2, uri, id, origin, webView);
  const savedState = await getSavedWebViewState(webViewId);
  await invoke$4('ExtensionHostWebView.load', webViewId, savedState);
  return {
    csp: iframeCsp,
    iframeSrc,
    origin,
    portId,
    sandbox
  };
};

const createAndLoadWebView = async (id, iframeSrc, sandbox, iframeCsp, credentialless, permissionPolicyString, frameTitle) => {
  await invoke$1('WebView.create', id, iframeSrc, sandbox, iframeCsp, credentialless, permissionPolicyString, frameTitle);

  // TODO maybe iframe waitForLoad is not needed. since it cannot be used detect errors anyway
  // and causes flash of unstyled content, maybe a better way would be to just send the
  // port and wait for the first port message
  await invoke$1('WebView.load', id);
};

const createSecondaryWebViewConnection = async (uid, origin, port) => {
  const portType = 'application';
  await invokeAndTransfer$1('WebView.setPort', uid, port, origin, portType);
};

const createWebViewConnection = async (uid, origin) => {
  const {
    port1,
    port2
  } = getPortTuple();
  const rpcPromise = MessagePortRpcParent.create({
    commandMap: {},
    isMessagePortOpen: false,
    messagePort: port2
  });
  const portType = 'test';
  await invokeAndTransfer$1('WebView.setPort', uid, port1, origin, portType);
  // TODO dispose rpc to avoid memory leak
  const rpc = await rpcPromise;
  return rpc;
};

const getPortQuadruple = () => {
  const {
    port1,
    port2
  } = new MessageChannel();
  const {
    port1: port3,
    port2: port4
  } = new MessageChannel();
  return {
    port1,
    port2,
    port3,
    port4
  };
};

const readFile = uri => {
  return invoke$3('FileSystem.readFile', uri);
};

const RE_PROTOCOL = /^([a-z-]+):\/\//;
const getProtocol = uri => {
  const protocolMatch = uri.match(RE_PROTOCOL);
  if (protocolMatch) {
    return protocolMatch[1];
  }
  return '';
};

const rpcs = Object.create(null);
const set$1 = (id, rpc) => {
  rpcs[id] = rpc;
};
const get$1 = id => {
  return rpcs[id];
};
const getAll$1 = () => {
  return rpcs;
};

// TODO if webViewId is provided,
// 1. read file as blob
// 2. send blob to webview
// 3. create objecturl in webview
// 4. send back objecturl to extension host worker
// 5. provide objectUrl to extension

const getRemoteUrlForWebView = async options => {
  object(options);
  const webView = get$1(options.id);
  object(webView);
  const rpcPromise = createWebViewConnection(webView.webViewUid, webView.origin);
  const blobPromise = invoke$3('FileSystem.getBlob', options.uri);
  const [rpc, blob] = await Promise.all([rpcPromise, blobPromise]);
  const objectUrl = await rpc.invoke('createObjectUrl', blob);
  return objectUrl;
};

let platform = 0;
const setPlatform = value => {
  platform = value;
};
const getPlatform = () => {
  return platform;
};

const getRemoteUrl = async options => {
  const {
    uri
  } = options;
  const platform = getPlatform() || Web;
  // TODO uri should always have protocol
  // then ask file system provider for remote url, for example disk file system provider or html file system provider
  const protocol = getProtocol(uri);
  if (platform === Remote && !protocol) {
    if (uri.startsWith('/')) {
      return `/remote${uri}`;
    }
    return `/remote/${uri}`;
  }
  if ((platform === Remote || platform === Electron) && protocol === 'file') {
    const rest = uri.slice(7);
    return `/remote${rest}`;
  }
  if (platform === Electron && !protocol) {
    if (uri.startsWith('/')) {
      return `/remote${uri}`;
    }
    return `/remote/${uri}`;
  }
  return getRemoteUrlForWebView(options);
};

const commandMap$1 = {
  'WebView.getRemoteUrl': getRemoteUrl,
  'WebView.readFile': readFile
};

const getWebViewWorkerRpc2 = async rpcInfo => {
  const rpc = await TransferMessagePortRpcParent.create({
    commandMap: commandMap$1,
    async send(port) {
      await invokeAndTransfer$2('WebView.createWebViewWorkerRpc2', rpcInfo, port);
    }
  });
  // TODO
  // 1. ask extension host worker to ask renderer worker to ask renderer process to create a worker with given url
  // 2. send the port through renderer worker to renderer process to the worker for a direct connection
  // TODO rpc module should start the port
  return rpc;
};

// TODO not part of this function but, for the webview webworker connection,
// send two ports to the iframe
// one port for builtin events like ctrl+shift+p keydown event for quickpick
// second port for the webview webworker connection

// this creates in total
// 1 iframe
// 1 worker
// 6 messageports

// one way to reduce the number of messageports could be to route the worker events
// through the iframe worker. However that could introduce some overhead / latency
// compared to direct connections

const interceptors = Object.create(null);
const add = (id, port) => {
  interceptors[id] = port;
};
const remove = id => {
  delete interceptors[id];
};
const getAll = () => {
  return Object.values(interceptors);
};
const isEmpty = () => {
  return Object.keys(interceptors).length === 0;
};

const notifyInterceptors = message => {
  if (isEmpty()) {
    return;
  }
  const ports = getAll();
  // TODO use rpc.invoke
  for (const port of ports) {
    port.postMessage({
      jsonrpc: '2.0',
      method: 'handleMessageCalled',
      params: [message]
    });
  }
};

const proxyPorts = (port1, port2) => {
  port1.addEventListener('message', event => {
    port2.postMessage(event.data);
  });
  port2.addEventListener('message', event => {
    port1.postMessage(event.data);
  });
};

const createWebWorkerRpc2 = async (rpcInfo, webView, savedState, uri, portId, webViewUid, origin) => {
  const rpc = await getWebViewWorkerRpc2(rpcInfo);
  const webViewInfo = {
    origin,
    portId: portId,
    rpc,
    webViewId: webView.id,
    webViewUid
  };
  set$1(portId, webViewInfo);

  // TODO this connection might not be needed
  await createWebViewConnection(webViewUid, origin);
  const {
    port1,
    port2,
    port3,
    port4
  } = getPortQuadruple();
  await createSecondaryWebViewConnection(webViewUid, origin, port1);

  // TODO remove message listeners on dispose
  port2.addEventListener('message', event => {
    notifyInterceptors(event.data);
  });
  port3.addEventListener('message', event => {
    notifyInterceptors(event.data);
  });
  proxyPorts(port2, port3);
  port1.start();
  port2.start();
  port3.start();
  port4.start();
  await rpc.invokeAndTransfer('_WebView.setPort', portId, port4);
  await rpc.invoke('_WebView.create', {
    id: portId,
    savedState,
    uri,
    webViewId: webView.id
  });
};

/**
 * @deprecated use getWebViewWorkerRpc2 instead
 */
const getWebViewWorkerRpc = async rpcInfo => {
  const {
    port1,
    port2
  } = getPortTuple();
  const rpcPromise = MessagePortRpcParent.create({
    commandMap: commandMap$1,
    isMessagePortOpen: true,
    messagePort: port2
  });
  await invokeAndTransfer$2('WebView.createWebViewWorkerRpc', rpcInfo, port1);
  const rpc = await rpcPromise;
  // TODO rpc module should start the port
  port2.start();
  return rpc;
};

const createWebViewRpc$1 = async (rpcInfo, webView, savedState, uri, portId, webViewUid, origin) => {
  // deprecated below
  // eslint-disable-next-line @typescript-eslint/no-deprecated
  const rpc = await getWebViewWorkerRpc(rpcInfo);
  const webViewInfo = {
    origin,
    portId: portId,
    rpc,
    webViewId: webView.id,
    webViewUid
  };
  set$1(portId, webViewInfo);
  await rpc.invoke('LoadFile.loadFile', rpcInfo.url);

  // TODO this connection might not be needed
  await createWebViewConnection(webViewUid, origin);
  const {
    port1,
    port2
  } = getPortTuple();
  await createSecondaryWebViewConnection(webViewUid, origin, port1);
  await rpc.invokeAndTransfer('_WebView.setPort', portId, port2);
  await rpc.invoke('_WebView.create', {
    id: portId,
    savedState,
    uri,
    webViewId: webView.id
  });
};

const createWebViewRpc = async (webView, savedState, uri, portId, webViewUid, origin) => {
  if (!webView || !webView.rpc || typeof webView.rpc !== 'string') {
    return;
  }
  const rpcInfo = await invoke$4('WebView.getRpcInfo', webView.rpc);
  if (rpcInfo && rpcInfo.type === 'web-worker-2') {
    return createWebWorkerRpc2(rpcInfo, webView, savedState, uri, portId, webViewUid, origin);
  }
  // legacy
  if (rpcInfo.type !== 'web-worker') {
    throw new Error(`only web worker rpc is supported for webviews`);
  }
  return createWebViewRpc$1(rpcInfo, webView, savedState, uri, portId, webViewUid, origin);
};

const getPreviewServerId = () => {
  // TODO
  return 1;
};

const getWebViewId = (webViews, uri) => {
  if (uri.startsWith('webview://')) {
    const webViewId = uri.slice('webview://'.length);
    return webViewId;
  }
  for (const webView of webViews) {
    for (const selector of webView.selector || []) {
      if (uri.endsWith(selector)) {
        return webView.id;
      }
    }
  }
  return '';
};

const getWebViewPort = (platform, locationPort) => {
  if (platform === Web) {
    return locationPort;
  }
  // TODO make port configurable
  return '3002';
};

const create3 = async ({
  assetDir,
  id,
  isGitpod,
  platform,
  uri,
  useNewWebViewHandler,
  webViewScheme
}) => {
  setPlatform(platform);
  let root = '';
  if (platform === Remote) {
    root = await invoke('Platform.getRoot');
  }
  const webViews = await getWebViews();
  const webViewId = getWebViewId(webViews, uri);
  const locationProtocol = getProtocol$1();
  const locationHost = getHost();
  const locationOrigin = getOrigin();
  const locationPort = getPort();
  const webViewPort = getWebViewPort(platform, locationPort);
  const previewServerId = getPreviewServerId();
  const iframeResult = getIframeSrc(webViews, webViewId, webViewPort, root, isGitpod, locationProtocol, locationHost, locationOrigin, platform, assetDir, webViewScheme, useNewWebViewHandler || false);
  if (!iframeResult) {
    return undefined;
  }
  const webView = getWebView(webViews, webViewId);
  const {
    iframeContent,
    iframeSrc,
    webViewRoot
  } = iframeResult;
  const frameAncestors = getWebViewFrameAncestors(locationProtocol, locationHost);
  const frameTitle = getWebViewTitle(webView);
  // TODO figure out order for events, e.g.
  // 1. activate extension, create webview and ports in parallel
  // 2. wait for webview to load (?)
  // 3. setup extension host worker rpc
  // 4. create webview in extension host worker and load content

  const csp = getWebViewCsp(webView);
  const sandbox = getIframeSandbox(webView, platform);
  const permissionPolicy = getIframePermissionPolicy(webView);
  const permissionPolicyString = permissionPolicy.join('; ');
  const iframeCsp = platform === Web ? csp : '';
  const credentialless = getCredentialLess(locationHost);

  // TODO remove this
  await invoke$2('ExtensionHostManagement.activateByEvent', `onWebView:${webViewId}`);
  const portId = create$2();
  const remotePathPrefix = '/remote';
  await register(previewServerId, webViewPort, frameAncestors, webViewRoot, csp, iframeContent, platform, webViewId, remotePathPrefix, useNewWebViewHandler);
  await createAndLoadWebView(id, iframeSrc, sandbox, iframeCsp, credentialless, permissionPolicyString, frameTitle);
  const origin = getWebViewOrigin(webViewPort, platform, webViewScheme, webViewId);
  const hasOldRpc = !webView || !webView.rpc || typeof webView.rpc !== 'string';
  if (hasOldRpc) {
    const {
      port1,
      port2
    } = getPortTuple();
    const portType = '';
    await setPort(id, port1, origin, portType);
    await invokeAndTransfer$2('ExtensionHostWebView.create', webViewId, port2, uri, id, origin, webView);
  }
  const savedState = await getSavedWebViewState(webViewId);
  if (hasOldRpc) {
    await invoke$4('ExtensionHostWebView.load', webViewId, savedState);
  }
  await createWebViewRpc(webView, savedState, uri, portId, id, origin);
  return {
    csp: iframeCsp,
    iframeSrc,
    origin,
    portId,
    sandbox
  };
};

const toCommandId = key => {
  const dotIndex = key.indexOf('.');
  return key.slice(dotIndex + 1);
};
const create = () => {
  const states = Object.create(null);
  const commandMapRef = {};
  return {
    clear() {
      for (const key of Object.keys(states)) {
        delete states[key];
      }
    },
    diff(uid, modules, numbers) {
      const {
        newState,
        oldState
      } = states[uid];
      const diffResult = [];
      for (let i = 0; i < modules.length; i++) {
        const fn = modules[i];
        if (!fn(oldState, newState)) {
          diffResult.push(numbers[i]);
        }
      }
      return diffResult;
    },
    dispose(uid) {
      delete states[uid];
    },
    get(uid) {
      return states[uid];
    },
    getCommandIds() {
      const keys = Object.keys(commandMapRef);
      const ids = keys.map(toCommandId);
      return ids;
    },
    getKeys() {
      return Object.keys(states).map(key => {
        return Number.parseInt(key);
      });
    },
    registerCommands(commandMap) {
      Object.assign(commandMapRef, commandMap);
    },
    set(uid, oldState, newState) {
      states[uid] = {
        newState,
        oldState
      };
    },
    wrapCommand(fn) {
      const wrapped = async (uid, ...args) => {
        const {
          newState,
          oldState
        } = states[uid];
        const newerState = await fn(newState, ...args);
        if (oldState === newerState || newState === newerState) {
          return;
        }
        const latestOld = states[uid];
        const latestNew = {
          ...latestOld.newState,
          ...newerState
        };
        states[uid] = {
          newState: latestNew,
          oldState: latestOld.oldState
        };
      };
      return wrapped;
    },
    wrapGetter(fn) {
      const wrapped = (uid, ...args) => {
        const {
          newState
        } = states[uid];
        return fn(newState, ...args);
      };
      return wrapped;
    }
  };
};

const {
  diff,
  get,
  getCommandIds,
  set,
  wrapCommand} = create();

const create4 = (id, uri, x, y, width, height, platform, assetDir, webViewScheme) => {
  number(platform);
  string(assetDir);
  const state = {
    assetDir,
    credentialless: true,
    csp: '',
    height,
    id,
    iframeSrc: '',
    iframeTitle: '',
    origin: '',
    platform,
    portId: 0,
    previewServerId: 1,
    sandbox: [],
    srcDoc: '',
    uri,
    webViewScheme,
    width,
    x: x - 1,
    y
  };
  set(state.id, state, state);
};

const isEqual$1 = (oldState, newState) => {
  return oldState === newState;
};

const isEqual = (oldState, newState) => {
  return oldState === newState;
};

const RenderItems = 1;
const RenderMove = 2;

const modules = [isEqual$1, isEqual];
const numbers = [RenderItems, RenderMove];

const diff2 = uid => {
  return diff(uid, modules, numbers);
};

const executeCommand = (method, ...params) => {
  // @ts-ignore
  return invoke$2('ExecuteExternalCommand.executeExternalCommand', method, ...params);
};

const getSecret = async key => {
  return invoke$3('WebView.getSecret', key);
};

const getWebViewInfo = webViewId => {
  const rpcs = getAll$1();
  for (const value of Object.values(rpcs)) {
    if (value.webViewId === webViewId) {
      return {
        origin: value.origin,
        uid: value.webViewUid
      };
    }
  }
  return undefined;
};

const handleIframeLoad = async state => {
  // TODO
  return {
    ...state
  };
};

const handleMessagePort2 = async (port, rpcId) => {
  object(port);
  const rpc = await PlainMessagePortRpcParent.create({
    commandMap: {},
    messagePort: port
  });
  if (rpcId) {
    set$4(rpcId, rpc);
  }
};

const initialize = async platform => {
  setPlatform(platform);
  const rpc = await TransferMessagePortRpcParent.create({
    commandMap: {},
    async send(port) {
      await sendMessagePortToExtensionHostWorker(port, 0);
    }
  });
  set$3(rpc);
};

const loadContent = async (state, savedState) => {
  const {
    assetDir,
    id,
    platform,
    uri,
    webViewScheme
  } = state;
  let root = '';
  if (platform === Remote) {
    root = await invoke('Platform.getRoot');
  }
  const webViews = await getWebViews();
  const webViewId = getWebViewId(webViews, uri);

  // TODO pass location props as arguments
  const locationProtocol = getProtocol$1();
  const locationHost = getHost();
  const locationOrigin = getOrigin();
  const locationPort = getPort();
  const webViewPort = getWebViewPort(platform, locationPort);
  const previewServerId = getPreviewServerId();
  const iframeResult = getIframeSrc(webViews, webViewId, webViewPort, root, false, locationProtocol, locationHost, locationOrigin, platform, assetDir, webViewScheme, true);
  if (!iframeResult) {
    return state;
  }
  const webView = getWebView(webViews, webViewId);
  const {
    iframeContent,
    iframeSrc,
    webViewRoot
  } = iframeResult;
  const frameAncestors = getWebViewFrameAncestors(locationProtocol, locationHost);
  const frameTitle = getWebViewTitle(webView);
  // TODO figure out order for events, e.g.
  // 1. activate extension, create webview and ports in parallel
  // 2. wait for webview to load (?)
  // 3. setup extension host worker rpc
  // 4. create webview in extension host worker and load content

  const csp = getWebViewCsp(webView);
  const sandbox = getIframeSandbox(webView, platform);
  const permissionPolicy = getIframePermissionPolicy(webView);
  const permissionPolicyString = permissionPolicy.join('; ');
  const iframeCsp = platform === Web ? csp : '';
  const credentialless = getCredentialLess(locationHost);

  // // TODO remove this
  await invoke$2('ExtensionHostManagement.activateByEvent', `onWebView:${webViewId}`);
  const portId = create$2();
  const useNewWebViewHandler = true;
  const remotePathPrefix = '/remote';
  await register(previewServerId, webViewPort, frameAncestors, webViewRoot, csp, iframeContent, platform, webViewId, remotePathPrefix, useNewWebViewHandler);
  await createAndLoadWebView(id, iframeSrc, sandbox, iframeCsp, credentialless, permissionPolicyString, frameTitle);
  const origin = getWebViewOrigin(webViewPort, platform, webViewScheme, webViewId);

  // const hasOldRpc = !webView || !webView.rpc || typeof webView.rpc !== 'string'

  // if (hasOldRpc) {
  //   const { port1, port2 } = GetPortTuple.getPortTuple()
  //   const portType = ''
  //   await SetPort.setPort(id, port1, origin, portType)
  //   await ExtensionHostWorker.invokeAndTransfer('ExtensionHostWebView.create', webViewId, port2, uri, id, origin, webView)
  // }

  // const savedState = await GetSavedWebViewState.getSavedWebViewState(webViewId)

  // if (hasOldRpc) {
  //   await ExtensionHostWorker.invoke('ExtensionHostWebView.load', webViewId, savedState)
  // }

  // TODO need to send port to webview to create connection

  // TODO maybe need to create hidden iframe first

  // TODO would need to wait somehow for iframe to be connected
  // setTimeout(async () => {
  await createWebViewRpc(webView, savedState, uri, portId, id, origin);
  // }, 0)

  // TODO maybe use  the createwebview api

  return {
    ...state,
    csp: iframeCsp,
    iframeSrc,
    iframeTitle: frameTitle,
    origin,
    portId,
    sandbox
  };
};

const renderItems = (oldState, newState) => {
  const {
    id
  } = newState;
  const dom = [{
    childCount: 0,
    className: 'iframe-wrapper',
    'data-id': id,
    type: Div
  }];
  return [SetDom2, id, dom];
};

const renderMove = (oldState, newState) => {
  const {
    id
  } = newState;
  const source = `.Workbench > iframe`;
  const target = `[data-id="${id}"].iframe-wrapper`;
  return ['Viewlet.move', id, source, target];
};

const getRenderer = diffType => {
  switch (diffType) {
    case RenderItems:
      return renderItems;
    case RenderMove:
      return renderMove;
    default:
      throw new Error('unknown renderer');
  }
};

const applyRender = (oldState, newState, diffResult) => {
  const commands = [];
  for (const item of diffResult) {
    const fn = getRenderer(item);
    const result = fn(oldState, newState);
    if (result.length > 0) {
      commands.push(result);
    }
  }
  return commands;
};

const render2 = (uid, diffResult) => {
  const {
    newState,
    oldState
  } = get(uid);
  set(uid, newState, newState);
  const commands = applyRender(oldState, newState, diffResult);
  return commands;
};

const HandleIframeLoad = 3;

const renderEventListeners = () => {
  return [{
    name: HandleIframeLoad,
    params: ['handleIframeLoad']
  }];
};

const saveState = async () => {
  const all = getAll$1();
  const serialized = [];
  for (const value of Object.values(all)) {
    try {
      const savedState = await value.rpc.invoke('WebView.saveState', value.portId);
      serialized.push({
        key: value.webViewId,
        value: savedState
      });
    } catch (error) {
      console.error(new VError(error, 'Failed to save state'));
      // ignore
    }
  }
  return serialized;
};

const registerInterceptor = async (id, port) => {
  add(id, port);
};
const unregisterInterceptor = async id => {
  remove(id);
};

const commandMap = {
  'Iframes.handleMessagePort': handleMessagePort2,
  'Iframes.initialize': initialize,
  'WebView.create2': create2,
  'WebView.create3': create3,
  'WebView.create4': create4,
  'WebView.diff2': diff2,
  'WebView.executeExternalCommand': executeCommand,
  'WebView.getCommandIds': getCommandIds,
  'WebView.getSecret': getSecret,
  'WebView.getWebViewInfo': getWebViewInfo,
  'WebView.handleIframeLoad': wrapCommand(handleIframeLoad),
  'WebView.loadContent': wrapCommand(loadContent),
  'WebView.registerInterceptor': registerInterceptor,
  'WebView.render2': render2,
  'WebView.renderEventListeners': renderEventListeners,
  'WebView.saveState': saveState,
  'WebView.unregisterInterceptor': unregisterInterceptor
};

const initializeRendererWorker = async () => {
  const rpc = await WebWorkerRpcClient.create({
    commandMap: commandMap
  });
  set$2(rpc);
};

const listen = async () => {
  await initializeRendererWorker();
};

const main = async () => {
  await listen();
};

main();
