mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-13 00:10:27 +00:00
- 将 == 改为 === 进行严格相等检查 - 修复未使用的变量,改为 _varName 格式 - 移除箭头函数中缺少的返回值 - 将 void 改为 undefined - 移除无用的构造函数 - 修复 Promise 参数命名规范 - 移除不必要的转义符 - 添加 Service Worker 全局变量声明 - 修复未使用的类型参数
308 lines
8.3 KiB
TypeScript
308 lines
8.3 KiB
TypeScript
/**
|
||
* 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 * as path from 'path';
|
||
import * as tty from 'tty';
|
||
import { Terminal, DEFAULT_COLS, DEFAULT_ROWS } from '@homebridge/node-pty-prebuilt-multiarch/src/terminal';
|
||
import { IProcessEnv, IPtyForkOptions, IPtyOpenOptions } from '@homebridge/node-pty-prebuilt-multiarch/src/interfaces';
|
||
import { ArgvOrCommandLine } from '@homebridge/node-pty-prebuilt-multiarch/src/types';
|
||
import { assign } from '@homebridge/node-pty-prebuilt-multiarch/src/utils';
|
||
import { pty_loader } from './prebuild-loader';
|
||
import { fileURLToPath } from 'url';
|
||
|
||
// 懒加载pty,避免在模块导入时立即执行pty_loader()
|
||
let _pty: any;
|
||
export const pty: any = new Proxy({}, {
|
||
get (_target, prop) {
|
||
if (!_pty) {
|
||
_pty = pty_loader();
|
||
}
|
||
return _pty[prop];
|
||
},
|
||
});
|
||
|
||
let helperPath: string;
|
||
helperPath = '../build/Release/spawn-helper';
|
||
const import__dirname = path.dirname(fileURLToPath(import.meta.url));
|
||
helperPath = path.resolve(import__dirname, helperPath);
|
||
helperPath = helperPath.replace('app.asar', 'app.asar.unpacked');
|
||
helperPath = helperPath.replace('node_modules.asar', 'node_modules.asar.unpacked');
|
||
|
||
const DEFAULT_FILE = 'sh';
|
||
const DEFAULT_NAME = 'xterm';
|
||
const DESTROY_SOCKET_TIMEOUT_MS = 200;
|
||
|
||
export class UnixTerminal extends Terminal {
|
||
protected override _fd: number;
|
||
protected override _pty: string;
|
||
|
||
protected override _file: string;
|
||
protected override _name: string;
|
||
|
||
protected override _readable: boolean;
|
||
protected override _writable: boolean;
|
||
|
||
private _boundClose: boolean = false;
|
||
private _emittedClose: boolean = false;
|
||
private _master: net.Socket | undefined;
|
||
private _slave: net.Socket | undefined;
|
||
|
||
public get master (): net.Socket | undefined { return this._master; }
|
||
public get slave (): net.Socket | undefined { return this._slave; }
|
||
|
||
constructor (file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions) {
|
||
super(opt);
|
||
|
||
if (typeof args === 'string') {
|
||
throw new Error('args as a string is not supported on unix.');
|
||
}
|
||
|
||
// Initialize arguments
|
||
args = args || [];
|
||
file = file || DEFAULT_FILE;
|
||
opt = opt || {};
|
||
opt.env = opt.env || process.env;
|
||
|
||
this._cols = opt.cols || DEFAULT_COLS;
|
||
this._rows = opt.rows || DEFAULT_ROWS;
|
||
const uid = opt.uid ?? -1;
|
||
const gid = opt.gid ?? -1;
|
||
const env: IProcessEnv = assign({}, opt.env);
|
||
|
||
if (opt.env === process.env) {
|
||
this._sanitizeEnv(env);
|
||
}
|
||
|
||
const cwd = opt.cwd || process.cwd();
|
||
env['PWD'] = 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: number, signal: number): void => {
|
||
// XXX Sometimes a data event is emitted after exit. Wait til socket is
|
||
// destroyed.
|
||
if (!this._emittedClose) {
|
||
if (this._boundClose) {
|
||
return;
|
||
}
|
||
this._boundClose = true;
|
||
// From macOS High Sierra 10.13.2 sometimes the socket never gets
|
||
// closed. A timeout is applied here to avoid the terminal never being
|
||
// destroyed when this occurs.
|
||
let timeout: NodeJS.Timeout | null = setTimeout(() => {
|
||
timeout = null;
|
||
// Destroying the socket now will cause the close event to fire
|
||
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);
|
||
};
|
||
|
||
// fork
|
||
const term = pty.fork(file, args, parsedEnv, cwd, this._cols, this._rows, uid, gid, (encoding === 'utf8'), helperPath, onexit);
|
||
|
||
this._socket = new tty.ReadStream(term.fd);
|
||
if (encoding !== null) {
|
||
this._socket.setEncoding(encoding as BufferEncoding);
|
||
}
|
||
|
||
// setup
|
||
this._socket.on('error', (err: any) => {
|
||
// NOTE: fs.ReadStream gets EAGAIN twice at first:
|
||
if (err.code) {
|
||
if (~err.code.indexOf('EAGAIN')) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
// close
|
||
this._close();
|
||
// EIO on exit from fs.ReadStream:
|
||
if (!this._emittedClose) {
|
||
this._emittedClose = true;
|
||
this.emit('close');
|
||
}
|
||
|
||
// EIO, happens when someone closes our child process: the only process in
|
||
// the terminal.
|
||
// node < 0.6.14: errno 5
|
||
// node >= 0.6.14: read EIO
|
||
if (err.code) {
|
||
if (~err.code.indexOf('errno 5') || ~err.code.indexOf('EIO')) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
// throw anything else
|
||
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');
|
||
});
|
||
|
||
this._forwardEvents();
|
||
}
|
||
|
||
protected _write (data: string): void {
|
||
this._socket.write(data);
|
||
}
|
||
|
||
/* Accessors */
|
||
get fd (): number { return this._fd; }
|
||
get ptsName (): string { return this._pty; }
|
||
|
||
/**
|
||
* openpty
|
||
*/
|
||
|
||
public static open (opt: IPtyOpenOptions): UnixTerminal {
|
||
const self: UnixTerminal = 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 === undefined ? 'utf8' : opt.encoding);
|
||
|
||
// open
|
||
const term: IUnixOpenProcess = pty.open(cols, rows);
|
||
|
||
self._master = new tty.ReadStream(term.master);
|
||
if (encoding !== null) {
|
||
self._master.setEncoding(encoding as BufferEncoding);
|
||
}
|
||
self._master.resume();
|
||
|
||
self._slave = new tty.ReadStream(term.slave);
|
||
if (encoding !== null) {
|
||
self._slave.setEncoding(encoding as BufferEncoding);
|
||
}
|
||
self._slave.resume();
|
||
|
||
self._socket = self._master;
|
||
self._pid = -1;
|
||
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 destroy (): void {
|
||
this._close();
|
||
|
||
// Need to close the read stream so node stops reading a dead file
|
||
// descriptor. Then we can safely SIGHUP the shell.
|
||
this._socket.once('close', () => {
|
||
this.kill('SIGHUP');
|
||
});
|
||
|
||
this._socket.destroy();
|
||
}
|
||
|
||
public kill (signal?: string): void {
|
||
try {
|
||
process.kill(this.pid, signal || 'SIGHUP');
|
||
} catch { /* swallow */ }
|
||
}
|
||
|
||
/**
|
||
* Gets the name of the process.
|
||
*/
|
||
public get process (): string {
|
||
if (process.platform === 'darwin') {
|
||
const title = pty.process(this._fd);
|
||
return (title !== 'kernel_task') ? title : this._file;
|
||
}
|
||
|
||
return pty.process(this._fd, this._pty) || this._file;
|
||
}
|
||
|
||
/**
|
||
* TTY
|
||
*/
|
||
|
||
public resize (cols: number, rows: number): void {
|
||
if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) {
|
||
throw new Error('resizing must be done using positive cols and rows');
|
||
}
|
||
pty.resize(this._fd, cols, rows);
|
||
this._cols = cols;
|
||
this._rows = rows;
|
||
}
|
||
|
||
public clear (): void {
|
||
|
||
}
|
||
|
||
private _sanitizeEnv (env: IProcessEnv): void {
|
||
// Make sure we didn't start our server from inside tmux.
|
||
delete env['TMUX'];
|
||
delete env['TMUX_PANE'];
|
||
|
||
// Make sure we didn't start our server from inside screen.
|
||
// http://web.mit.edu/gnu/doc/html/screen_20.html
|
||
delete env['STY'];
|
||
delete env['WINDOW'];
|
||
|
||
// Delete some variables that might confuse our terminal.
|
||
delete env['WINDOWID'];
|
||
delete env['TERMCAP'];
|
||
delete env['COLUMNS'];
|
||
delete env['LINES'];
|
||
}
|
||
}
|