|
|
|
|
|
|
|
|
|
|
|
|
|
import { Socket } from 'net'; |
|
import { EventEmitter } from 'events'; |
|
import { ITerminal, IPtyForkOptions } from './interfaces'; |
|
import { EventEmitter2, IEvent } from './eventEmitter2'; |
|
import { IExitEvent } from './types'; |
|
|
|
export const DEFAULT_COLS: number = 80; |
|
export const DEFAULT_ROWS: number = 24; |
|
|
|
|
|
|
|
|
|
|
|
|
|
const FLOW_CONTROL_PAUSE = '\x13'; |
|
const FLOW_CONTROL_RESUME = '\x11'; |
|
|
|
export abstract class Terminal implements ITerminal { |
|
protected _socket: Socket; |
|
protected _pid: number; |
|
protected _fd: number; |
|
protected _pty: any; |
|
|
|
protected _file: string; |
|
protected _name: string; |
|
protected _cols: number; |
|
protected _rows: number; |
|
|
|
protected _readable: boolean; |
|
protected _writable: boolean; |
|
|
|
protected _internalee: EventEmitter; |
|
private _flowControlPause: string; |
|
private _flowControlResume: string; |
|
public handleFlowControl: boolean; |
|
|
|
private _onData = new EventEmitter2<string>(); |
|
public get onData(): IEvent<string> { return this._onData.event; } |
|
private _onExit = new EventEmitter2<IExitEvent>(); |
|
public get onExit(): IEvent<IExitEvent> { return this._onExit.event; } |
|
|
|
public get pid(): number { return this._pid; } |
|
public get cols(): number { return this._cols; } |
|
public get rows(): number { return this._rows; } |
|
|
|
constructor(opt?: IPtyForkOptions) { |
|
|
|
this._internalee = new EventEmitter(); |
|
|
|
if (!opt) { |
|
return; |
|
} |
|
|
|
|
|
|
|
this._checkType('name', opt.name ? opt.name : null, 'string'); |
|
this._checkType('cols', opt.cols ? opt.cols : null, 'number'); |
|
this._checkType('rows', opt.rows ? opt.rows : null, 'number'); |
|
this._checkType('cwd', opt.cwd ? opt.cwd : null, 'string'); |
|
this._checkType('env', opt.env ? opt.env : null, 'object'); |
|
this._checkType('uid', opt.uid ? opt.uid : null, 'number'); |
|
this._checkType('gid', opt.gid ? opt.gid : null, 'number'); |
|
this._checkType('encoding', opt.encoding ? opt.encoding : null, 'string'); |
|
|
|
|
|
this.handleFlowControl = !!(opt.handleFlowControl); |
|
this._flowControlPause = opt.flowControlPause || FLOW_CONTROL_PAUSE; |
|
this._flowControlResume = opt.flowControlResume || FLOW_CONTROL_RESUME; |
|
} |
|
|
|
protected abstract _write(data: string): void; |
|
|
|
public write(data: string): void { |
|
if (this.handleFlowControl) { |
|
|
|
if (data === this._flowControlPause) { |
|
this.pause(); |
|
return; |
|
} |
|
if (data === this._flowControlResume) { |
|
this.resume(); |
|
return; |
|
} |
|
} |
|
|
|
this._write(data); |
|
} |
|
|
|
protected _forwardEvents(): void { |
|
this.on('data', e => this._onData.fire(e)); |
|
this.on('exit', (exitCode, signal) => this._onExit.fire({ exitCode, signal })); |
|
} |
|
|
|
private _checkType(name: string, value: any, type: string): void { |
|
if (value && typeof value !== type) { |
|
throw new Error(`${name} must be a ${type} (not a ${typeof value})`); |
|
} |
|
} |
|
|
|
|
|
public end(data: string): void { |
|
this._socket.end(data); |
|
} |
|
|
|
|
|
public pipe(dest: any, options: any): any { |
|
return this._socket.pipe(dest, options); |
|
} |
|
|
|
|
|
public pause(): Socket { |
|
return this._socket.pause(); |
|
} |
|
|
|
|
|
public resume(): Socket { |
|
return this._socket.resume(); |
|
} |
|
|
|
|
|
public setEncoding(encoding: string | null): void { |
|
if ((<any>this._socket)._decoder) { |
|
delete (<any>this._socket)._decoder; |
|
} |
|
if (encoding) { |
|
this._socket.setEncoding(encoding); |
|
} |
|
} |
|
|
|
public addListener(eventName: string, listener: (...args: any[]) => any): void { this.on(eventName, listener); } |
|
public on(eventName: string, listener: (...args: any[]) => any): void { |
|
if (eventName === 'close') { |
|
this._internalee.on('close', listener); |
|
return; |
|
} |
|
this._socket.on(eventName, listener); |
|
} |
|
|
|
public emit(eventName: string, ...args: any[]): any { |
|
if (eventName === 'close') { |
|
return this._internalee.emit.apply(this._internalee, arguments); |
|
} |
|
return this._socket.emit.apply(this._socket, arguments); |
|
} |
|
|
|
public listeners(eventName: string): Function[] { |
|
return this._socket.listeners(eventName); |
|
} |
|
|
|
public removeListener(eventName: string, listener: (...args: any[]) => any): void { |
|
this._socket.removeListener(eventName, listener); |
|
} |
|
|
|
public removeAllListeners(eventName: string): void { |
|
this._socket.removeAllListeners(eventName); |
|
} |
|
|
|
public once(eventName: string, listener: (...args: any[]) => any): void { |
|
this._socket.once(eventName, listener); |
|
} |
|
|
|
public abstract resize(cols: number, rows: number): void; |
|
public abstract destroy(): void; |
|
public abstract kill(signal?: string): void; |
|
|
|
public abstract get process(): string; |
|
public abstract get master(): Socket; |
|
public abstract get slave(): Socket; |
|
|
|
protected _close(): void { |
|
this._socket.writable = false; |
|
this._socket.readable = false; |
|
this.write = () => {}; |
|
this.end = () => {}; |
|
this._writable = false; |
|
this._readable = false; |
|
} |
|
|
|
protected _parseEnv(env: {[key: string]: string}): string[] { |
|
const keys = Object.keys(env || {}); |
|
const pairs = []; |
|
|
|
for (let i = 0; i < keys.length; i++) { |
|
pairs.push(keys[i] + '=' + env[keys[i]]); |
|
} |
|
|
|
return pairs; |
|
} |
|
} |
|
|