/**
 * Copyright (c) 2012-2015, Christopher Jeffrey (MIT License)
 * Copyright (c) 2016, Daniel Imms (MIT License).
 * Copyright (c) 2018, Microsoft Corporation (MIT License).
 */ import * as net from 'net' ; import { Terminal , DEFAULT_COLS , DEFAULT_ROWS } from './terminal' ; import { IProcessEnv , IPtyForkOptions , IPtyOpenOptions } from './interfaces' ; import { ArgvOrCommandLine } from './types' ; import { assign , loadNative } from './utils' ; const pty = loadNative ( 'pty' ) ; const DEFAULT_FILE = 'sh' ; const DEFAULT_NAME = 'xterm' ; const DESTROY_SOCKET_TIMEOUT_MS = 200 ; export class UnixTerminal extends Terminal { protected _fd : number ; protected _pty : string ; protected _file : string ; protected _name : string ; protected _readable : boolean ; protected _writable : boolean ; private _boundClose : boolean ; private _emittedClose : boolean ; private _master : net . Socket ; private _slave : net . Socket ; public get master ( ) : net . Socket { return this . _master ; } public get slave ( ) : net . Socket { return this . _slave ; } constructor ( file ? , args ? , opt ? ) { super ( opt ) ; if ( typeof args === 'string' ) { throw new Error ( 'args as a string is not supported on unix.' ) ; } args = args || [ ] ; file = file || DEFAULT_FILE ; opt = opt || { } ; opt . env = opt . env || process . env ; const cols = opt . cols || DEFAULT_COLS ; const rows = opt . rows || DEFAULT_ROWS ; const uid = opt . uid || - 1 ; const gid = opt . gid || - 1 ; const env = assign ( { } , opt . env ) ; if ( opt . env === process . env ) { this . _sanitizeEnv ( env ) ; } const cwd = opt . cwd || process . cwd ( ) ; const name = opt . name || env . TERM || DEFAULT_NAME ; env . TERM = name ; const parsedEnv = this . _parseEnv ( env ) ; const encoding = ( opt . encoding === undefined ? 'utf8' : opt . encoding ) ; const onexit = ( code , signal ) => { if ( ! this . _emittedClose ) { if ( this . _boundClose ) { return ; } this . _boundClose = true ; let timeout = setTimeout ( ( ) => { timeout = null ; this . _socket . destroy ( ) ; } , DESTROY_SOCKET_TIMEOUT_MS ) ; this . once ( 'close' , ( ) => { if ( timeout !== null ) { clearTimeout ( timeout ) ; } this . emit ( 'exit' , code , signal ) ; } ) ; return ; } this . emit ( 'exit' , code , signal ) ; } ; const term = pty . fork ( file , args , parsedEnv , cwd , cols , rows , uid , gid , ( encoding === 'utf8' ) , onexit ) ; this . _socket = new PipeSocket ( term . fd ) ; if ( encoding !== null ) { this . _socket . setEncoding ( encoding ) ; } this . _socket . on ( 'error' , ( err ) => { if ( err . code ) { if ( ~ err . code . indexOf ( 'EAGAIN' ) ) { return ; } } this . _close ( ) ; if ( ! this . _emittedClose ) { this . _emittedClose = true ; this . emit ( 'close' ) ; } if ( err . code ) { if ( ~ err . code . indexOf ( 'errno 5' ) || ~ err . code . indexOf ( 'EIO' ) ) { return ; } } if ( this . listeners ( 'error' ) . length < 2 ) { throw err ; } } ) ; this . _pid = term . pid ; this . _fd = term . fd ; this . _pty = term . pty ; this . _file = file ; this . _name = name ; this . _readable = true ; this . _writable = true ; this . _socket . on ( 'close' , ( ) => { if ( this . _emittedClose ) { return ; } this . _emittedClose = true ; this . _close ( ) ; this . emit ( 'close' ) ; } ) ; } /**
     * openpty
     */ public static open ( opt ) { const self = Object . create ( UnixTerminal . prototype ) ; opt = opt || { } ; if ( arguments . length > 1 ) { opt = { cols : arguments [ 1 ] , rows : arguments [ 2 ] } ; } const cols = opt . cols || DEFAULT_COLS ; const rows = opt . rows || DEFAULT_ROWS ; const encoding = opt . encoding ? 'utf8' : opt . encoding ; const term = pty . open ( cols , rows ) ; self . _master = new PipeSocket ( < number > term . master ) ; self . _master . setEncoding ( encoding ) ; self . _master . resume ( ) ; self . _slave = new PipeSocket ( term . slave ) ; self . _slave . setEncoding ( encoding ) ; self . _slave . resume ( ) ; self . _socket = self . _master ; self . _pid = null ; self . _fd = term . master ; self . _pty = term . pty ; self . _file = process . argv [ 0 ] || 'node' ; self . _name = process . env . TERM || '' ; self . _readable = true ; self . _writable = true ; self . _socket . on ( 'error' , err => { self . _close ( ) ; if ( self . listeners ( 'error' ) . length < 2 ) { throw err ; } } ) ; self . _socket . on ( 'close' , ( ) => { self . _close ( ) ; } ) ; return self ; } public write ( data ) { this . _socket . write ( data ) ; } public destroy ( ) { this . _close ( ) ; this . _socket . once ( 'close' , ( ) => { this . kill ( 'SIGHUP' ) ; } ) ; this . _socket . destroy ( ) ; } public kill ( signal ? ) { try { process . kill ( this . pid , signal || 'SIGHUP' ) ; } catch ( e ) { } } /**
     * Gets the name of the process.
     */ public get process ( ) : string { return pty . process ( this . _fd , this . _pty ) || this . _file ; } /**
     * TTY
     */ public resize ( cols , rows ) { pty . resize ( this . _fd , cols , rows ) ; } private _sanitizeEnv ( env ) { delete env [ 'TMUX' ] ; delete env [ 'TMUX_PANE' ] ; delete env [ 'STY' ] ; delete env [ 'WINDOW' ] ; delete env [ 'WINDOWID' ] ; delete env [ 'TERMCAP' ] ; delete env [ 'COLUMNS' ] ; delete env [ 'LINES' ] ; } } /**
 * Wraps net.Socket to force the handle type "PIPE" by temporarily overwriting
 * tty_wrap.guessHandleType.
 * See: https://github.com/chjj/pty.js/issues/103
 */ class PipeSocket extends net . Socket { constructor ( fd ) { const tty = ( < any > process ) . binding ( 'tty_wrap' ) ; const guessHandleType = tty . guessHandleType ; tty . guessHandleType = ( ) => 'PIPE' ; super ( { fd : < any > fd } ) ; tty . guessHandleType = guessHandleType ; } }