"use strict";

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

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

var _events = require("events");

var _server = require("./server");

var _channel = require("./channel");

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());
  });
};

const debugE = (0, _debug.default)('plugins/bash-like/pty/stdio-channel-proxy-stderr');
const debugW = (0, _debug.default)('plugins/bash-like/pty/stdio-channel-proxy');
const debugK = (0, _debug.default)('plugins/bash-like/pty/stdio-channel-kui');
const MARKER = '\n';

function heartbeat() {
  debugW('heartbeat');
  this.isAlive = true;
}
/**
 * stdin/stdout channel
 *
 */


class StdioChannelWebsocketSide extends _events.EventEmitter {
  constructor(wss) {
    super();
    this.readyState = _channel.ReadyState.CONNECTING;
    this.wss = wss;
  }

  init(child, pollInterval = 30000) {
    return __awaiter(this, void 0, void 0, function* () {
      debugW('StdioChannelWebsocketSide.init');
      this.wss.on('error', err => {
        debugW('websocket error', err);
      });
      this.wss.on('connection', ws => {
        debugW('got connection');
        this.ws = ws; // upstream client has sent data downstream; forward it to the subprocess

        ws.on('message', data => {
          debugW('forwarding message downstream');
          child.stdin.write(`${data}${MARKER}`);
        }); // on pong response, indicate we remain alive

        ws.on('pong', heartbeat);
        ws.on('close', () => {
          debugW('killing child process, because client connection is dead');
          child.kill();
        });
      });
      const self = this; // eslint-disable-line @typescript-eslint/no-this-alias

      setInterval(function ping() {
        self.wss['clients'].forEach(function each(ws) {
          if (ws.isAlive === false) {
            debugW('killing child process, because client connection did not respond to ping');
            child.kill();
            return ws.terminate();
          } // assume it is dead until we get a pong


          ws.isAlive = false;
          ws.ping(() => {// intentional no-op
          });
        });
      }, pollInterval);
      child.on('exit', code => {
        debugW('child exit', code);
        this.emit('exit', code);
      });
      child.stderr.on('data', data => {
        if (data.length > 0) {
          debugE(data.toString());
        }
      }); // underlying pty has emitted data from the subprocess

      let pending;
      child.stdout.on('data', data => {
        const msg = data.toString();

        if (!msg.endsWith(MARKER)) {
          if (!pending) {
            pending = msg;
          } else {
            pending += msg;
          }
        } else {
          this.send(pending ? `${pending}${msg}` : msg);
          pending = undefined;
        }
      });
    });
  }
  /** Forcibly close the channel */


  close() {
    debugW('closing stdio channel');
    this.emit('exit');
  }
  /** emit 'message' on the other side */


  send(msg) {
    debugW('send', this.readyState === _channel.ReadyState.OPEN);

    if (msg === `open${MARKER}`) {
      this.readyState = _channel.ReadyState.OPEN; // this signals exec.js that the websocket is ready; see channel.once('open', ...)

      this.emit('open');
    } else if (this.readyState === _channel.ReadyState.OPEN) {
      msg.split(MARKER).filter(_ => _).forEach(_ => {
        debugW('forwarding child output upstream');
        this.ws.send(`${_}${MARKER}`);
      });
    }
  } // eslint-disable-next-line @typescript-eslint/no-explicit-any


  removeEventListener(eventType, handler) {
    this.off(eventType, handler);
  }

}
/**
 * stdin/stdout channel
 *
 */


exports.StdioChannelWebsocketSide = StdioChannelWebsocketSide;

class StdioChannelKuiSide extends _events.EventEmitter {
  constructor() {
    super(...arguments);
    this.readyState = _channel.ReadyState.OPEN;
  }

  init(onExit) {
    return __awaiter(this, void 0, void 0, function* () {
      debugK('StdioChannelKuiSide.init'); // await onConnection(await disableBashSessions())(this)

      yield (0, _server.onConnection)(onExit)(this);
      process.stdin.on('data', data => {
        data.toString().split(MARKER).filter(_ => _).forEach(_ => {
          debugK('input', _);
          this.emit('message', _);
        });
      });
      this.send('open');
    });
  }
  /** Forcibly close the channel */


  close() {
    debugW('closing stdio channel');
    this.emit('close');
  }
  /** emit 'message' on the other side */


  send(msg) {
    if (this.readyState === _channel.ReadyState.OPEN) {
      // debugK('send', msg)
      process.stdout.write(`${msg}${MARKER}`);
    }
  } // eslint-disable-next-line @typescript-eslint/no-explicit-any


  removeEventListener(eventType, handler) {
    this.off(eventType, handler);
  }

}

exports.StdioChannelKuiSide = StdioChannelKuiSide;