"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _debug = _interopRequireDefault(require("debug"));

var _uuid = require("uuid");

var _core = require("@kui-shell/core");

var _config = require("./config");

var _pluginBashLike = require("@kui-shell/plugin-bash-like");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/*
 * Copyright 2019 IBM Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var __awaiter = void 0 && (void 0).__awaiter || function (thisArg, _arguments, P, generator) {
  function adopt(value) {
    return value instanceof P ? value : new P(function (resolve) {
      resolve(value);
    });
  }

  return new (P || (P = Promise))(function (resolve, reject) {
    function fulfilled(value) {
      try {
        step(generator.next(value));
      } catch (e) {
        reject(e);
      }
    }

    function rejected(value) {
      try {
        step(generator["throw"](value));
      } catch (e) {
        reject(e);
      }
    }

    function step(result) {
      result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
    }

    step((generator = generator.apply(thisArg, _arguments || [])).next());
  });
};
/* eslint-disable @typescript-eslint/explicit-member-accessibility */


const debug = (0, _debug.default)('plugins/proxy-support/executor');
/** we may want to directly evaluate certain commands in the browser */

const directEvaluator = new _core.DirectReplEval();

function renderDom(content) {
  const dom = document.createElement(content.nodeType || 'span');

  if (content.className.length > 0) {
    dom.className = content.className;
  } else if (content.classList.classList.length > 0) {
    content.classList.classList.forEach(_ => {
      dom.classList.add(_);
    });
  } // TODO attrs


  if (content.innerText) {
    dom.innerText = content.innerText;
  } else if (content.children && content.children.length > 0) {
    content.children.forEach(child => {
      dom.appendChild(renderDom(child));
    });
  }

  return dom;
}
/**
 * A repl.exec implementation that proxies to the packages/proxy container
 *
 */


class ProxyEvaluator {
  constructor() {
    this.name = 'ProxyEvaluator';
    /**
     * The proxy server configuration.
     *
     */

    this.proxyServerConfig = (0, _config.config)().then(_ => _.proxyServer);
  }

  apply(command, execOptions, evaluator, args) {
    return __awaiter(this, void 0, void 0, function* () {
      debug('apply', evaluator);
      debug('execOptions', execOptions);
      const proxyServerConfig = yield this.proxyServerConfig;

      if ((0, _config.isDisabled)(proxyServerConfig) || (0, _core.isCommandHandlerWithEvents)(evaluator) && evaluator.options && !evaluator.options.requiresLocal) {
        debug('delegating to direct evaluator');
        return directEvaluator.apply(command, execOptions, evaluator, args);
      } else {
        const execOptionsForInvoke = (0, _core.withLanguage)(Object.assign({}, execOptions, {
          block: undefined,
          nextBlock: undefined,
          isProxied: true,
          cwd: process.env.PWD,
          env: process.env && execOptions.env ? Object.assign(Object.assign({}, process.env), execOptions.env) : execOptions.env ? execOptions.env : process.env,
          credentials: (0, _core.getValidCredentials)(),
          tab: undefined,
          rawResponse: true // we will post-process the response

        }));

        if (command !== 'bash websocket open') {
          // eslint-disable-next-line no-async-promise-executor
          return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
            const uuid = (0, _uuid.v4)();
            debug('delegating to proxy websocket', command, uuid);
            const channel = yield (0, _pluginBashLike.getSessionForTab)(args.tab);
            const msg = {
              type: 'request',
              cmdline: command,
              uuid,
              cwd: process.env.PWD,
              execOptions: execOptionsForInvoke
            };
            channel.send(JSON.stringify(msg));
            const MARKER = '\n';
            let raw = '';

            const onMessage = ({
              data
            }) => {
              // debug('raw', uuid, data)
              raw += data;

              if (data.endsWith(MARKER)) {
                raw += data;

                try {
                  raw.split(MARKER).filter(_ => _).forEach(_ => {
                    const response = JSON.parse(_);

                    if (response.uuid === uuid) {
                      channel.removeEventListener('message', onMessage);
                      const code = response.response.code || response.response.statusCode;

                      if (code !== undefined && code !== 200) {
                        if ((0, _core.isUsageError)(response.response)) {
                          // e.g. k get -h
                          debug('rejecting as usage error', response);
                          reject(response.response);
                        } else {
                          // e.g. k get pod nonExistantName
                          debug('rejecting as other error', response);
                          const err = new Error(response.response.message);
                          err.stack = response.response.stack;
                          err.code = code; // see https://github.com/IBM/kui/issues/3318

                          err.statusCode = response.response.statusCode !== undefined ? response.response.statusCode : code;
                          err.body = response.response;
                          reject(err);
                        }
                      } else if (_core.ElementMimic.isFakeDom(response.response)) {
                        debug('rendering fakedom', response.response);
                        resolve(renderDom(response.response));
                      } else if (_core.ElementMimic.isFakeDom(response.response.content)) {
                        debug('rendering fakedom content', response.response.content);
                        response.response.content = renderDom(response.response.content);
                        resolve(response.response);
                      } else {
                        debug('response', response);
                        resolve(response.response);
                      }
                    }
                  });
                } catch (err) {
                  console.error('error handling response', raw);
                  console.error(err);
                  reject(new Error('Internal Error'));
                }
              }
            };

            channel.on('message', onMessage);
          }));
        }

        debug('delegating to proxy exec', command);
        const body = {
          command,
          execOptions: execOptionsForInvoke
        };
        debug('sending body', body);

        try {
          const proxyHref = /KUI_PROXY_COHOSTED=true/.test(document.cookie) ? `${window.location.pathname.replace(/index\.html/, '')}exec` : new URL(proxyServerConfig.url, window.location.origin).href;
          debug('proxy server url', proxyHref);

          const invokeRemote = () => new Promise(resolve => {
            const xhr = new XMLHttpRequest();
            xhr.open('POST', proxyHref);
            xhr.responseType = 'json';
            xhr.withCredentials = true;
            xhr.setRequestHeader('Content-Type', 'application/json');
            xhr.setRequestHeader('Accept', 'application/json');
            xhr.addEventListener('error', () => {
              if (xhr.readyState === 4 && xhr.status === 0) {
                // this means connection refused or other inability to connect to the proxy server
                resolve({
                  statusCode: 503,
                  code: 503,
                  body: 'Connection refused'
                });
              } else {
                console.error('error in xhr', xhr.status, xhr);
                resolve(xhr.response || 'Internal Error');
              }
            });
            xhr.addEventListener('load', () => {
              resolve({
                statusCode: xhr.status,
                body: xhr.response.response
              });
            }); // proxyServerConfig.needleOptions

            xhr.send(JSON.stringify(body));
          }); // eslint-disable-next-line @typescript-eslint/no-explicit-any


          const response = yield window['webview-proxy'] ? window['webview-proxy'](body) : invokeRemote(); // debug('response', response)

          if (response.statusCode !== 200) {
            debug('rethrowing non-200 response', response); // to trigger the catch just below

            const err = new Error(response.body);
            err.code = err.statusCode = response.statusCode;
            err.body = response.body;
            throw err;
          } else {
            /*
             * try to unwind the fakedom for now
             * TODO: we need a type guard to prevent fakedom object being passed to the proxy executor
             * see related issue: https://github.com/IBM/kui/issues/1687
             *
             */
            if (_core.ElementMimic.isFakeDom(response.body)) {
              debug('catch a fakedom, try to unwind');

              if (response.body.innerText) {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                return response.body.innerText;
              } else {
                const err = new Error('Internal Error: Fakedom objects are not accepted by proxy executor');
                err['code'] = 500;
                throw err;
              }
            }

            return response.body;
          }
        } catch (err) {
          debug('proxy execution resulted in an error, recasting to local exception', err.code, err.message, err.body, err);

          if (err.body && (0, _core.isUsageError)(err.body)) {
            debug('the error is a usage error, rethrowing as such');
            throw new _core.UsageError({
              message: err.body.raw.message,
              usage: err.body.raw.usage,
              code: err.body.code,
              extra: err.body.extra
            });
          } else {
            const error = new Error(err.body && err.body.message || (typeof err.body === 'string' ? err.body : err.message || 'Internal error'));
            error.code = error.statusCode = err.body && err.body.code || err.code || err.statusCode;
            debug('using this code', error.code);
            throw error;
          }
        }
      }
    });
  }

}

var _default = ProxyEvaluator;
exports.default = _default;