201 lines
5.2 KiB
JavaScript
201 lines
5.2 KiB
JavaScript
/* jslint node: true */
|
|
'use strict';
|
|
|
|
// ENiGMA½
|
|
const Log = require('./logger.js').log;
|
|
const renegadeToAnsi = require('./color_codes.js').renegadeToAnsi;
|
|
const Config = require('./config.js').get;
|
|
const iconv = require('iconv-lite');
|
|
const assert = require('assert');
|
|
const _ = require('lodash');
|
|
|
|
exports.ClientTerminal = ClientTerminal;
|
|
|
|
function ClientTerminal(output) {
|
|
this.output = output;
|
|
|
|
let outputEncoding = 'cp437';
|
|
assert(iconv.encodingExists(outputEncoding));
|
|
|
|
// convert line feeds such as \n -> \r\n
|
|
this.convertLF = true;
|
|
|
|
this.syncTermFontsEnabled = false;
|
|
|
|
//
|
|
// Some terminal we handle specially
|
|
// They can also be found in this.env{}
|
|
//
|
|
let termType = 'unknown';
|
|
let termHeight = 0;
|
|
let termWidth = 0;
|
|
let termClient = 'unknown';
|
|
|
|
this.currentSyncFont = 'not_set';
|
|
|
|
// Raw values set by e.g. telnet NAWS, ENVIRONMENT, etc.
|
|
this.env = {};
|
|
|
|
Object.defineProperty(this, 'outputEncoding', {
|
|
get: function () {
|
|
return outputEncoding;
|
|
},
|
|
set: function (enc) {
|
|
if (iconv.encodingExists(enc)) {
|
|
Log.info(
|
|
{ encoding: enc, currentEncoding: outputEncoding },
|
|
`Output encoding changed to ${enc}`
|
|
);
|
|
outputEncoding = enc;
|
|
} else {
|
|
Log.warn({ encoding: enc }, 'Unknown encoding');
|
|
}
|
|
},
|
|
});
|
|
|
|
Object.defineProperty(this, 'termType', {
|
|
get: function () {
|
|
return termType;
|
|
},
|
|
set: function (ttype) {
|
|
termType = ttype.toLowerCase();
|
|
|
|
Log.debug(
|
|
{ encoding: this.outputEncoding },
|
|
`Terminal type changed to ${termType}; Adjusting output encoding`
|
|
);
|
|
|
|
if (this.isNixTerm()) {
|
|
this.outputEncoding = 'utf8';
|
|
} else {
|
|
this.outputEncoding = 'cp437';
|
|
}
|
|
|
|
// :TODO: according to this: http://mud-dev.wikidot.com/article:telnet-client-identification
|
|
// Windows telnet will send "VTNT". If so, set termClient='windows'
|
|
// there are some others on the page as well
|
|
},
|
|
});
|
|
|
|
Object.defineProperty(this, 'termWidth', {
|
|
get: function () {
|
|
return termWidth;
|
|
},
|
|
set: function (width) {
|
|
if (width > 0) {
|
|
termWidth = width;
|
|
}
|
|
},
|
|
});
|
|
|
|
Object.defineProperty(this, 'termHeight', {
|
|
get: function () {
|
|
return termHeight;
|
|
},
|
|
set: function (height) {
|
|
if (height > 0) {
|
|
termHeight = height;
|
|
}
|
|
},
|
|
});
|
|
|
|
Object.defineProperty(this, 'termClient', {
|
|
get: function () {
|
|
return termClient;
|
|
},
|
|
set: function (tc) {
|
|
termClient = tc;
|
|
|
|
Log.debug({ termClient: this.termClient }, 'Set known terminal client');
|
|
},
|
|
});
|
|
}
|
|
|
|
ClientTerminal.prototype.disconnect = function () {
|
|
this.output = null;
|
|
};
|
|
|
|
ClientTerminal.prototype.isNixTerm = function () {
|
|
//
|
|
// Standard *nix type terminals
|
|
//
|
|
if (this.termType.startsWith('xterm')) {
|
|
return true;
|
|
}
|
|
|
|
const utf8TermList = Config().term.utf8TermList;
|
|
return utf8TermList.includes(this.termType);
|
|
};
|
|
|
|
ClientTerminal.prototype.isANSI = function () {
|
|
//
|
|
// ANSI terminals should be encoded to CP437
|
|
//
|
|
// Some terminal types provided by Mercyful Fate / Enthral:
|
|
// ANSI-BBS
|
|
// PC-ANSI
|
|
// QANSI
|
|
// SCOANSI
|
|
// VT100
|
|
// QNX
|
|
//
|
|
// Reports from various terminals
|
|
//
|
|
// NetRunner v2.00beta 20
|
|
// * This version adds 256 colors and reports as "ansi-256color"
|
|
//
|
|
// syncterm:
|
|
// * SyncTERM
|
|
//
|
|
// xterm:
|
|
// * PuTTY
|
|
//
|
|
// ansi-bbs:
|
|
// * fTelnet
|
|
//
|
|
// pcansi:
|
|
// * ZOC
|
|
//
|
|
// screen:
|
|
// * ConnectBot (Android)
|
|
//
|
|
// linux:
|
|
// * JuiceSSH (note: TERM=linux also)
|
|
//
|
|
const cp437TermList = Config().term.cp437TermList;
|
|
return cp437TermList.includes(this.termType);
|
|
};
|
|
|
|
// :TODO: probably need to update these to convert IAC (0xff) -> IACIAC (escape it)
|
|
|
|
ClientTerminal.prototype.write = function (s, convertLineFeeds, cb) {
|
|
this.rawWrite(this.encode(s, convertLineFeeds), cb);
|
|
};
|
|
|
|
ClientTerminal.prototype.rawWrite = function (s, cb) {
|
|
if (this.output && this.output.writable) {
|
|
this.output.write(s, err => {
|
|
if (cb) {
|
|
return cb(err);
|
|
}
|
|
|
|
if (err) {
|
|
Log.warn({ error: err.message }, 'Failed writing to socket');
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
ClientTerminal.prototype.pipeWrite = function (s, cb) {
|
|
this.write(renegadeToAnsi(s, this), null, cb); // null = use default for |convertLineFeeds|
|
|
};
|
|
|
|
ClientTerminal.prototype.encode = function (s, convertLineFeeds) {
|
|
convertLineFeeds = _.isBoolean(convertLineFeeds) ? convertLineFeeds : this.convertLF;
|
|
|
|
if (convertLineFeeds && _.isString(s)) {
|
|
s = s.replace(/\n/g, '\r\n');
|
|
}
|
|
return iconv.encode(s, this.outputEncoding);
|
|
};
|