green-screen-proxy 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +32 -0
- package/dist/hp6530/connection.d.ts +51 -0
- package/dist/hp6530/connection.js +258 -0
- package/dist/hp6530/constants.d.ts +64 -0
- package/dist/hp6530/constants.js +135 -0
- package/dist/hp6530/encoder.d.ts +37 -0
- package/dist/hp6530/encoder.js +89 -0
- package/dist/hp6530/parser.d.ts +45 -0
- package/dist/hp6530/parser.js +255 -0
- package/dist/hp6530/screen.d.ts +104 -0
- package/dist/hp6530/screen.js +252 -0
- package/dist/mock/mock-routes.d.ts +2 -0
- package/dist/mock/mock-routes.js +231 -0
- package/dist/protocols/hp6530-handler.d.ts +29 -0
- package/dist/protocols/hp6530-handler.js +64 -0
- package/dist/protocols/index.d.ts +11 -0
- package/dist/protocols/index.js +27 -0
- package/dist/protocols/tn3270-handler.d.ts +26 -0
- package/dist/protocols/tn3270-handler.js +61 -0
- package/dist/protocols/tn5250-handler.d.ts +26 -0
- package/dist/protocols/tn5250-handler.js +62 -0
- package/dist/protocols/types.d.ts +59 -0
- package/dist/protocols/types.js +7 -0
- package/dist/protocols/vt-handler.d.ts +30 -0
- package/dist/protocols/vt-handler.js +67 -0
- package/dist/routes.d.ts +2 -0
- package/dist/routes.js +141 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +34 -0
- package/dist/session.d.ts +32 -0
- package/dist/session.js +88 -0
- package/dist/tn3270/connection.d.ts +31 -0
- package/dist/tn3270/connection.js +266 -0
- package/dist/tn3270/constants.d.ts +262 -0
- package/dist/tn3270/constants.js +261 -0
- package/dist/tn3270/encoder.d.ts +24 -0
- package/dist/tn3270/encoder.js +97 -0
- package/dist/tn3270/parser.d.ts +22 -0
- package/dist/tn3270/parser.js +284 -0
- package/dist/tn3270/screen.d.ts +89 -0
- package/dist/tn3270/screen.js +207 -0
- package/dist/tn5250/connection.d.ts +41 -0
- package/dist/tn5250/connection.js +254 -0
- package/dist/tn5250/constants.d.ts +128 -0
- package/dist/tn5250/constants.js +156 -0
- package/dist/tn5250/ebcdic.d.ts +10 -0
- package/dist/tn5250/ebcdic.js +89 -0
- package/dist/tn5250/encoder.d.ts +30 -0
- package/dist/tn5250/encoder.js +121 -0
- package/dist/tn5250/parser.d.ts +33 -0
- package/dist/tn5250/parser.js +412 -0
- package/dist/tn5250/screen.d.ts +80 -0
- package/dist/tn5250/screen.js +155 -0
- package/dist/vt/connection.d.ts +45 -0
- package/dist/vt/connection.js +229 -0
- package/dist/vt/constants.d.ts +97 -0
- package/dist/vt/constants.js +163 -0
- package/dist/vt/encoder.d.ts +30 -0
- package/dist/vt/encoder.js +55 -0
- package/dist/vt/parser.d.ts +36 -0
- package/dist/vt/parser.js +534 -0
- package/dist/vt/screen.d.ts +101 -0
- package/dist/vt/screen.js +424 -0
- package/dist/websocket.d.ts +6 -0
- package/dist/websocket.js +50 -0
- package/package.json +57 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { SCREEN } from './constants.js';
|
|
3
|
+
export class ScreenBuffer {
|
|
4
|
+
rows;
|
|
5
|
+
cols;
|
|
6
|
+
/** Character buffer stored as UTF-8 characters */
|
|
7
|
+
buffer;
|
|
8
|
+
/** Attribute buffer (display attribute per cell) */
|
|
9
|
+
attrBuffer;
|
|
10
|
+
fields = [];
|
|
11
|
+
cursorRow = 0;
|
|
12
|
+
cursorCol = 0;
|
|
13
|
+
constructor(rows = SCREEN.ROWS_24, cols = SCREEN.COLS_80) {
|
|
14
|
+
this.rows = rows;
|
|
15
|
+
this.cols = cols;
|
|
16
|
+
const size = rows * cols;
|
|
17
|
+
this.buffer = new Array(size).fill(' ');
|
|
18
|
+
this.attrBuffer = new Array(size).fill(0x20); // normal
|
|
19
|
+
}
|
|
20
|
+
get size() {
|
|
21
|
+
return this.rows * this.cols;
|
|
22
|
+
}
|
|
23
|
+
/** Convert row,col to linear offset */
|
|
24
|
+
offset(row, col) {
|
|
25
|
+
return row * this.cols + col;
|
|
26
|
+
}
|
|
27
|
+
/** Convert linear offset to row,col */
|
|
28
|
+
toRowCol(offset) {
|
|
29
|
+
return {
|
|
30
|
+
row: Math.floor(offset / this.cols),
|
|
31
|
+
col: offset % this.cols,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Set a character at row,col */
|
|
35
|
+
setChar(row, col, char) {
|
|
36
|
+
const off = this.offset(row, col);
|
|
37
|
+
if (off >= 0 && off < this.size) {
|
|
38
|
+
this.buffer[off] = char;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Get a character at row,col */
|
|
42
|
+
getChar(row, col) {
|
|
43
|
+
const off = this.offset(row, col);
|
|
44
|
+
return off >= 0 && off < this.size ? this.buffer[off] : ' ';
|
|
45
|
+
}
|
|
46
|
+
/** Set attribute at row,col */
|
|
47
|
+
setAttr(row, col, attr) {
|
|
48
|
+
const off = this.offset(row, col);
|
|
49
|
+
if (off >= 0 && off < this.size) {
|
|
50
|
+
this.attrBuffer[off] = attr;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Set character at a linear address */
|
|
54
|
+
setCharAt(addr, char) {
|
|
55
|
+
if (addr >= 0 && addr < this.size) {
|
|
56
|
+
this.buffer[addr] = char;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Set attribute at a linear address */
|
|
60
|
+
setAttrAt(addr, attr) {
|
|
61
|
+
if (addr >= 0 && addr < this.size) {
|
|
62
|
+
this.attrBuffer[addr] = attr;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Clear the entire screen */
|
|
66
|
+
clear() {
|
|
67
|
+
this.buffer.fill(' ');
|
|
68
|
+
this.attrBuffer.fill(0x20);
|
|
69
|
+
this.fields = [];
|
|
70
|
+
this.cursorRow = 0;
|
|
71
|
+
this.cursorCol = 0;
|
|
72
|
+
}
|
|
73
|
+
/** Fill range [start, end) with a character */
|
|
74
|
+
fillRange(start, end, char) {
|
|
75
|
+
for (let i = start; i < end && i < this.size; i++) {
|
|
76
|
+
this.buffer[i] = char;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Get the content of a field as a string */
|
|
80
|
+
getFieldValue(field) {
|
|
81
|
+
const start = this.offset(field.row, field.col);
|
|
82
|
+
return this.buffer.slice(start, start + field.length).join('');
|
|
83
|
+
}
|
|
84
|
+
/** Set the content of a field */
|
|
85
|
+
setFieldValue(field, value) {
|
|
86
|
+
const start = this.offset(field.row, field.col);
|
|
87
|
+
for (let i = 0; i < field.length; i++) {
|
|
88
|
+
this.buffer[start + i] = i < value.length ? value[i] : ' ';
|
|
89
|
+
}
|
|
90
|
+
field.modified = true;
|
|
91
|
+
}
|
|
92
|
+
/** Find the field at cursor position */
|
|
93
|
+
getFieldAtCursor() {
|
|
94
|
+
return this.getFieldAt(this.cursorRow, this.cursorCol);
|
|
95
|
+
}
|
|
96
|
+
/** Find the field containing a given position */
|
|
97
|
+
getFieldAt(row, col) {
|
|
98
|
+
const pos = this.offset(row, col);
|
|
99
|
+
for (const field of this.fields) {
|
|
100
|
+
const start = this.offset(field.row, field.col);
|
|
101
|
+
if (pos >= start && pos < start + field.length) {
|
|
102
|
+
return field;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
/** Whether a field is an input field (not bypass/protected) */
|
|
108
|
+
isInputField(field) {
|
|
109
|
+
return (field.ffw1 & 0x20) === 0; // BYPASS bit not set
|
|
110
|
+
}
|
|
111
|
+
/** Whether a field is highlighted (high intensity) */
|
|
112
|
+
isHighlighted(field) {
|
|
113
|
+
return field.attribute === 0x22;
|
|
114
|
+
}
|
|
115
|
+
/** Whether a field is reverse video */
|
|
116
|
+
isReverse(field) {
|
|
117
|
+
return field.attribute === 0x21;
|
|
118
|
+
}
|
|
119
|
+
/** Whether a field is non-display (password) */
|
|
120
|
+
isNonDisplay(field) {
|
|
121
|
+
return field.attribute === 0x27;
|
|
122
|
+
}
|
|
123
|
+
/** Convert screen buffer to the ScreenData format expected by the frontend */
|
|
124
|
+
toScreenData() {
|
|
125
|
+
// Build content as newline-separated rows
|
|
126
|
+
const lines = [];
|
|
127
|
+
for (let r = 0; r < this.rows; r++) {
|
|
128
|
+
const start = r * this.cols;
|
|
129
|
+
lines.push(this.buffer.slice(start, start + this.cols).join(''));
|
|
130
|
+
}
|
|
131
|
+
const content = lines.join('\n');
|
|
132
|
+
// Map fields to frontend format
|
|
133
|
+
const fields = this.fields.map(f => ({
|
|
134
|
+
row: f.row,
|
|
135
|
+
col: f.col,
|
|
136
|
+
length: f.length,
|
|
137
|
+
is_input: this.isInputField(f),
|
|
138
|
+
is_protected: !this.isInputField(f),
|
|
139
|
+
is_highlighted: this.isHighlighted(f) || undefined,
|
|
140
|
+
is_reverse: this.isReverse(f) || undefined,
|
|
141
|
+
}));
|
|
142
|
+
// Generate screen signature
|
|
143
|
+
const hash = createHash('md5').update(content).digest('hex').substring(0, 12);
|
|
144
|
+
return {
|
|
145
|
+
content,
|
|
146
|
+
cursor_row: this.cursorRow,
|
|
147
|
+
cursor_col: this.cursorCol,
|
|
148
|
+
rows: this.rows,
|
|
149
|
+
cols: this.cols,
|
|
150
|
+
fields,
|
|
151
|
+
screen_signature: hash,
|
|
152
|
+
timestamp: new Date().toISOString(),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
export interface VTConnectionOptions {
|
|
3
|
+
terminalType?: string;
|
|
4
|
+
rows?: number;
|
|
5
|
+
cols?: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Manages a TCP/Telnet connection for VT terminal sessions.
|
|
9
|
+
*
|
|
10
|
+
* Unlike TN5250/TN3270, VT data is a continuous byte stream — there is no
|
|
11
|
+
* IAC EOR record framing. Telnet IAC commands are handled inline and stripped
|
|
12
|
+
* from the data stream; all remaining bytes are emitted as 'data' events.
|
|
13
|
+
*/
|
|
14
|
+
export declare class VTConnection extends EventEmitter {
|
|
15
|
+
private socket;
|
|
16
|
+
private host;
|
|
17
|
+
private port;
|
|
18
|
+
private connected;
|
|
19
|
+
private recvBuffer;
|
|
20
|
+
private terminalType;
|
|
21
|
+
private rows;
|
|
22
|
+
private cols;
|
|
23
|
+
get isConnected(): boolean;
|
|
24
|
+
get remoteHost(): string;
|
|
25
|
+
get remotePort(): number;
|
|
26
|
+
connect(host: string, port: number, options?: VTConnectionOptions): Promise<void>;
|
|
27
|
+
disconnect(): void;
|
|
28
|
+
/** Send raw bytes over the socket */
|
|
29
|
+
sendRaw(data: Buffer): void;
|
|
30
|
+
private cleanup;
|
|
31
|
+
private onData;
|
|
32
|
+
/**
|
|
33
|
+
* Process the receive buffer. Strip out Telnet IAC commands and emit
|
|
34
|
+
* the remaining application data as 'data' events.
|
|
35
|
+
*/
|
|
36
|
+
private processBuffer;
|
|
37
|
+
/** Find IAC SE after position `start` (which points to IAC SB) */
|
|
38
|
+
private findSubnegEnd;
|
|
39
|
+
private handleNegotiation;
|
|
40
|
+
private handleSubnegotiation;
|
|
41
|
+
private sendTerminalType;
|
|
42
|
+
/** Send NAWS (Negotiate About Window Size) subnegotiation */
|
|
43
|
+
private sendNAWS;
|
|
44
|
+
private sendTelnet;
|
|
45
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import * as net from 'net';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import { TELNET, DEFAULT_TERMINAL_TYPE, DEFAULT_ROWS, DEFAULT_COLS } from './constants.js';
|
|
4
|
+
/**
|
|
5
|
+
* Manages a TCP/Telnet connection for VT terminal sessions.
|
|
6
|
+
*
|
|
7
|
+
* Unlike TN5250/TN3270, VT data is a continuous byte stream — there is no
|
|
8
|
+
* IAC EOR record framing. Telnet IAC commands are handled inline and stripped
|
|
9
|
+
* from the data stream; all remaining bytes are emitted as 'data' events.
|
|
10
|
+
*/
|
|
11
|
+
export class VTConnection extends EventEmitter {
|
|
12
|
+
socket = null;
|
|
13
|
+
host = '';
|
|
14
|
+
port = 23;
|
|
15
|
+
connected = false;
|
|
16
|
+
recvBuffer = Buffer.alloc(0);
|
|
17
|
+
terminalType = DEFAULT_TERMINAL_TYPE;
|
|
18
|
+
rows = DEFAULT_ROWS;
|
|
19
|
+
cols = DEFAULT_COLS;
|
|
20
|
+
get isConnected() {
|
|
21
|
+
return this.connected;
|
|
22
|
+
}
|
|
23
|
+
get remoteHost() {
|
|
24
|
+
return this.host;
|
|
25
|
+
}
|
|
26
|
+
get remotePort() {
|
|
27
|
+
return this.port;
|
|
28
|
+
}
|
|
29
|
+
connect(host, port, options) {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
if (this.socket) {
|
|
32
|
+
this.disconnect();
|
|
33
|
+
}
|
|
34
|
+
this.host = host;
|
|
35
|
+
this.port = port;
|
|
36
|
+
this.recvBuffer = Buffer.alloc(0);
|
|
37
|
+
if (options?.terminalType)
|
|
38
|
+
this.terminalType = options.terminalType;
|
|
39
|
+
if (options?.rows)
|
|
40
|
+
this.rows = options.rows;
|
|
41
|
+
if (options?.cols)
|
|
42
|
+
this.cols = options.cols;
|
|
43
|
+
this.socket = new net.Socket();
|
|
44
|
+
this.socket.setTimeout(30000);
|
|
45
|
+
const onError = (err) => {
|
|
46
|
+
this.cleanup();
|
|
47
|
+
reject(err);
|
|
48
|
+
};
|
|
49
|
+
this.socket.once('error', onError);
|
|
50
|
+
this.socket.connect(port, host, () => {
|
|
51
|
+
this.connected = true;
|
|
52
|
+
this.socket.removeListener('error', onError);
|
|
53
|
+
this.socket.on('error', (err) => {
|
|
54
|
+
this.emit('error', err);
|
|
55
|
+
this.cleanup();
|
|
56
|
+
});
|
|
57
|
+
this.socket.on('close', () => {
|
|
58
|
+
this.cleanup();
|
|
59
|
+
this.emit('disconnected');
|
|
60
|
+
});
|
|
61
|
+
this.socket.on('timeout', () => {
|
|
62
|
+
this.emit('error', new Error('Connection timeout'));
|
|
63
|
+
});
|
|
64
|
+
this.socket.on('data', (data) => this.onData(data));
|
|
65
|
+
this.emit('connected');
|
|
66
|
+
resolve();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
disconnect() {
|
|
71
|
+
if (this.socket) {
|
|
72
|
+
this.socket.destroy();
|
|
73
|
+
this.cleanup();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Send raw bytes over the socket */
|
|
77
|
+
sendRaw(data) {
|
|
78
|
+
if (this.socket && this.connected) {
|
|
79
|
+
this.socket.write(data);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
cleanup() {
|
|
83
|
+
this.connected = false;
|
|
84
|
+
if (this.socket) {
|
|
85
|
+
this.socket.removeAllListeners();
|
|
86
|
+
this.socket.destroy();
|
|
87
|
+
this.socket = null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
onData(data) {
|
|
91
|
+
this.recvBuffer = Buffer.concat([this.recvBuffer, data]);
|
|
92
|
+
this.processBuffer();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Process the receive buffer. Strip out Telnet IAC commands and emit
|
|
96
|
+
* the remaining application data as 'data' events.
|
|
97
|
+
*/
|
|
98
|
+
processBuffer() {
|
|
99
|
+
const appData = [];
|
|
100
|
+
let i = 0;
|
|
101
|
+
while (i < this.recvBuffer.length) {
|
|
102
|
+
const byte = this.recvBuffer[i];
|
|
103
|
+
if (byte === TELNET.IAC) {
|
|
104
|
+
if (i + 1 >= this.recvBuffer.length) {
|
|
105
|
+
// Incomplete IAC sequence — keep remainder for next chunk
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
const cmd = this.recvBuffer[i + 1];
|
|
109
|
+
// IAC IAC = escaped 0xFF literal byte
|
|
110
|
+
if (cmd === TELNET.IAC) {
|
|
111
|
+
appData.push(0xff);
|
|
112
|
+
i += 2;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
// Subnegotiation: IAC SB ... IAC SE
|
|
116
|
+
if (cmd === TELNET.SB) {
|
|
117
|
+
const seIdx = this.findSubnegEnd(i);
|
|
118
|
+
if (seIdx === -1)
|
|
119
|
+
break; // Wait for more data
|
|
120
|
+
const subData = this.recvBuffer.subarray(i + 2, seIdx);
|
|
121
|
+
this.handleSubnegotiation(subData);
|
|
122
|
+
i = seIdx + 2; // skip past IAC SE
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
// DO/DONT/WILL/WONT: 3-byte commands
|
|
126
|
+
if (cmd === TELNET.DO || cmd === TELNET.DONT || cmd === TELNET.WILL || cmd === TELNET.WONT) {
|
|
127
|
+
if (i + 2 >= this.recvBuffer.length)
|
|
128
|
+
break; // Wait for option byte
|
|
129
|
+
const option = this.recvBuffer[i + 2];
|
|
130
|
+
this.handleNegotiation(cmd, option);
|
|
131
|
+
i += 3;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
// Other 2-byte IAC commands (GA, NOP, etc.) — skip
|
|
135
|
+
i += 2;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
// Regular data byte
|
|
139
|
+
appData.push(byte);
|
|
140
|
+
i++;
|
|
141
|
+
}
|
|
142
|
+
// Keep unprocessed bytes
|
|
143
|
+
this.recvBuffer = this.recvBuffer.subarray(i);
|
|
144
|
+
// Emit application data
|
|
145
|
+
if (appData.length > 0) {
|
|
146
|
+
this.emit('data', Buffer.from(appData));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/** Find IAC SE after position `start` (which points to IAC SB) */
|
|
150
|
+
findSubnegEnd(start) {
|
|
151
|
+
for (let j = start + 2; j < this.recvBuffer.length - 1; j++) {
|
|
152
|
+
if (this.recvBuffer[j] === TELNET.IAC && this.recvBuffer[j + 1] === TELNET.SE) {
|
|
153
|
+
return j;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return -1;
|
|
157
|
+
}
|
|
158
|
+
handleNegotiation(cmd, option) {
|
|
159
|
+
switch (cmd) {
|
|
160
|
+
case TELNET.DO:
|
|
161
|
+
if (option === TELNET.OPT_TTYPE ||
|
|
162
|
+
option === TELNET.OPT_NAWS ||
|
|
163
|
+
option === TELNET.OPT_BINARY ||
|
|
164
|
+
option === TELNET.OPT_SGA) {
|
|
165
|
+
this.sendTelnet(TELNET.WILL, option);
|
|
166
|
+
// After agreeing to NAWS, immediately send window size
|
|
167
|
+
if (option === TELNET.OPT_NAWS) {
|
|
168
|
+
this.sendNAWS();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
this.sendTelnet(TELNET.WONT, option);
|
|
173
|
+
}
|
|
174
|
+
break;
|
|
175
|
+
case TELNET.WILL:
|
|
176
|
+
if (option === TELNET.OPT_ECHO ||
|
|
177
|
+
option === TELNET.OPT_SGA ||
|
|
178
|
+
option === TELNET.OPT_BINARY) {
|
|
179
|
+
this.sendTelnet(TELNET.DO, option);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
this.sendTelnet(TELNET.DONT, option);
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
case TELNET.DONT:
|
|
186
|
+
this.sendTelnet(TELNET.WONT, option);
|
|
187
|
+
break;
|
|
188
|
+
case TELNET.WONT:
|
|
189
|
+
this.sendTelnet(TELNET.DONT, option);
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
handleSubnegotiation(data) {
|
|
194
|
+
if (data.length === 0)
|
|
195
|
+
return;
|
|
196
|
+
const option = data[0];
|
|
197
|
+
if (option === TELNET.OPT_TTYPE && data.length >= 2 && data[1] === TELNET.TTYPE_SEND) {
|
|
198
|
+
this.sendTerminalType();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
sendTerminalType() {
|
|
202
|
+
const typeStr = this.terminalType;
|
|
203
|
+
const buf = Buffer.alloc(4 + typeStr.length + 2);
|
|
204
|
+
let i = 0;
|
|
205
|
+
buf[i++] = TELNET.IAC;
|
|
206
|
+
buf[i++] = TELNET.SB;
|
|
207
|
+
buf[i++] = TELNET.OPT_TTYPE;
|
|
208
|
+
buf[i++] = TELNET.TTYPE_IS;
|
|
209
|
+
for (let j = 0; j < typeStr.length; j++) {
|
|
210
|
+
buf[i++] = typeStr.charCodeAt(j);
|
|
211
|
+
}
|
|
212
|
+
buf[i++] = TELNET.IAC;
|
|
213
|
+
buf[i++] = TELNET.SE;
|
|
214
|
+
this.sendRaw(buf);
|
|
215
|
+
}
|
|
216
|
+
/** Send NAWS (Negotiate About Window Size) subnegotiation */
|
|
217
|
+
sendNAWS() {
|
|
218
|
+
const buf = Buffer.from([
|
|
219
|
+
TELNET.IAC, TELNET.SB, TELNET.OPT_NAWS,
|
|
220
|
+
(this.cols >> 8) & 0xff, this.cols & 0xff,
|
|
221
|
+
(this.rows >> 8) & 0xff, this.rows & 0xff,
|
|
222
|
+
TELNET.IAC, TELNET.SE,
|
|
223
|
+
]);
|
|
224
|
+
this.sendRaw(buf);
|
|
225
|
+
}
|
|
226
|
+
sendTelnet(cmd, option) {
|
|
227
|
+
this.sendRaw(Buffer.from([TELNET.IAC, cmd, option]));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VT100/VT220/VT320 terminal constants and escape sequences.
|
|
3
|
+
*/
|
|
4
|
+
export declare const ESC = 27;
|
|
5
|
+
export declare const CSI_CHAR = 91;
|
|
6
|
+
export declare const CSI = "\u001B[";
|
|
7
|
+
export declare const NUL = 0;
|
|
8
|
+
export declare const BEL = 7;
|
|
9
|
+
export declare const BS = 8;
|
|
10
|
+
export declare const HT = 9;
|
|
11
|
+
export declare const LF = 10;
|
|
12
|
+
export declare const VT = 11;
|
|
13
|
+
export declare const FF = 12;
|
|
14
|
+
export declare const CR = 13;
|
|
15
|
+
export declare const SO = 14;
|
|
16
|
+
export declare const SI = 15;
|
|
17
|
+
export declare const DEL = 127;
|
|
18
|
+
export declare const TELNET: {
|
|
19
|
+
readonly IAC: 255;
|
|
20
|
+
readonly DONT: 254;
|
|
21
|
+
readonly DO: 253;
|
|
22
|
+
readonly WONT: 252;
|
|
23
|
+
readonly WILL: 251;
|
|
24
|
+
readonly SB: 250;
|
|
25
|
+
readonly SE: 240;
|
|
26
|
+
readonly GA: 249;
|
|
27
|
+
readonly NOP: 241;
|
|
28
|
+
readonly OPT_BINARY: 0;
|
|
29
|
+
readonly OPT_ECHO: 1;
|
|
30
|
+
readonly OPT_SGA: 3;
|
|
31
|
+
readonly OPT_TTYPE: 24;
|
|
32
|
+
readonly OPT_NAWS: 31;
|
|
33
|
+
readonly TTYPE_IS: 0;
|
|
34
|
+
readonly TTYPE_SEND: 1;
|
|
35
|
+
};
|
|
36
|
+
export declare const DEFAULT_ROWS = 24;
|
|
37
|
+
export declare const DEFAULT_COLS = 80;
|
|
38
|
+
export declare const TERMINAL_TYPES: {
|
|
39
|
+
readonly VT220: "VT220";
|
|
40
|
+
readonly VT320: "VT320";
|
|
41
|
+
readonly XTERM: "xterm";
|
|
42
|
+
};
|
|
43
|
+
export declare const DEFAULT_TERMINAL_TYPE: "VT220";
|
|
44
|
+
export declare const VT_KEYS: Record<string, string>;
|
|
45
|
+
export declare const SGR: {
|
|
46
|
+
readonly RESET: 0;
|
|
47
|
+
readonly BOLD: 1;
|
|
48
|
+
readonly DIM: 2;
|
|
49
|
+
readonly ITALIC: 3;
|
|
50
|
+
readonly UNDERLINE: 4;
|
|
51
|
+
readonly BLINK: 5;
|
|
52
|
+
readonly RAPID_BLINK: 6;
|
|
53
|
+
readonly REVERSE: 7;
|
|
54
|
+
readonly HIDDEN: 8;
|
|
55
|
+
readonly STRIKETHROUGH: 9;
|
|
56
|
+
readonly NORMAL_INTENSITY: 22;
|
|
57
|
+
readonly NO_ITALIC: 23;
|
|
58
|
+
readonly NO_UNDERLINE: 24;
|
|
59
|
+
readonly NO_BLINK: 25;
|
|
60
|
+
readonly NO_REVERSE: 27;
|
|
61
|
+
readonly NO_HIDDEN: 28;
|
|
62
|
+
readonly NO_STRIKETHROUGH: 29;
|
|
63
|
+
readonly FG_BLACK: 30;
|
|
64
|
+
readonly FG_RED: 31;
|
|
65
|
+
readonly FG_GREEN: 32;
|
|
66
|
+
readonly FG_YELLOW: 33;
|
|
67
|
+
readonly FG_BLUE: 34;
|
|
68
|
+
readonly FG_MAGENTA: 35;
|
|
69
|
+
readonly FG_CYAN: 36;
|
|
70
|
+
readonly FG_WHITE: 37;
|
|
71
|
+
readonly FG_DEFAULT: 39;
|
|
72
|
+
readonly BG_BLACK: 40;
|
|
73
|
+
readonly BG_RED: 41;
|
|
74
|
+
readonly BG_GREEN: 42;
|
|
75
|
+
readonly BG_YELLOW: 43;
|
|
76
|
+
readonly BG_BLUE: 44;
|
|
77
|
+
readonly BG_MAGENTA: 45;
|
|
78
|
+
readonly BG_CYAN: 46;
|
|
79
|
+
readonly BG_WHITE: 47;
|
|
80
|
+
readonly BG_DEFAULT: 49;
|
|
81
|
+
readonly FG_BRIGHT_BLACK: 90;
|
|
82
|
+
readonly FG_BRIGHT_RED: 91;
|
|
83
|
+
readonly FG_BRIGHT_GREEN: 92;
|
|
84
|
+
readonly FG_BRIGHT_YELLOW: 93;
|
|
85
|
+
readonly FG_BRIGHT_BLUE: 94;
|
|
86
|
+
readonly FG_BRIGHT_MAGENTA: 95;
|
|
87
|
+
readonly FG_BRIGHT_CYAN: 96;
|
|
88
|
+
readonly FG_BRIGHT_WHITE: 97;
|
|
89
|
+
readonly BG_BRIGHT_BLACK: 100;
|
|
90
|
+
readonly BG_BRIGHT_RED: 101;
|
|
91
|
+
readonly BG_BRIGHT_GREEN: 102;
|
|
92
|
+
readonly BG_BRIGHT_YELLOW: 103;
|
|
93
|
+
readonly BG_BRIGHT_BLUE: 104;
|
|
94
|
+
readonly BG_BRIGHT_MAGENTA: 105;
|
|
95
|
+
readonly BG_BRIGHT_CYAN: 106;
|
|
96
|
+
readonly BG_BRIGHT_WHITE: 107;
|
|
97
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VT100/VT220/VT320 terminal constants and escape sequences.
|
|
3
|
+
*/
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Control characters
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
export const ESC = 0x1b;
|
|
8
|
+
export const CSI_CHAR = 0x5b; // '['
|
|
9
|
+
export const CSI = '\x1b['; // Control Sequence Introducer
|
|
10
|
+
export const NUL = 0x00;
|
|
11
|
+
export const BEL = 0x07;
|
|
12
|
+
export const BS = 0x08;
|
|
13
|
+
export const HT = 0x09;
|
|
14
|
+
export const LF = 0x0a;
|
|
15
|
+
export const VT = 0x0b;
|
|
16
|
+
export const FF = 0x0c;
|
|
17
|
+
export const CR = 0x0d;
|
|
18
|
+
export const SO = 0x0e; // Shift Out (G1 charset)
|
|
19
|
+
export const SI = 0x0f; // Shift In (G0 charset)
|
|
20
|
+
export const DEL = 0x7f;
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Telnet constants (standard RFC 854 / RFC 855)
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
export const TELNET = {
|
|
25
|
+
IAC: 0xff,
|
|
26
|
+
DONT: 0xfe,
|
|
27
|
+
DO: 0xfd,
|
|
28
|
+
WONT: 0xfc,
|
|
29
|
+
WILL: 0xfb,
|
|
30
|
+
SB: 0xfa,
|
|
31
|
+
SE: 0xf0,
|
|
32
|
+
GA: 0xf9,
|
|
33
|
+
NOP: 0xf1,
|
|
34
|
+
// Options
|
|
35
|
+
OPT_BINARY: 0x00,
|
|
36
|
+
OPT_ECHO: 0x01,
|
|
37
|
+
OPT_SGA: 0x03, // Suppress Go Ahead
|
|
38
|
+
OPT_TTYPE: 0x18, // Terminal Type
|
|
39
|
+
OPT_NAWS: 0x1f, // Negotiate About Window Size
|
|
40
|
+
// Subnegotiation
|
|
41
|
+
TTYPE_IS: 0x00,
|
|
42
|
+
TTYPE_SEND: 0x01,
|
|
43
|
+
};
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Screen defaults
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
export const DEFAULT_ROWS = 24;
|
|
48
|
+
export const DEFAULT_COLS = 80;
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Terminal type strings
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
export const TERMINAL_TYPES = {
|
|
53
|
+
VT220: 'VT220',
|
|
54
|
+
VT320: 'VT320',
|
|
55
|
+
XTERM: 'xterm',
|
|
56
|
+
};
|
|
57
|
+
export const DEFAULT_TERMINAL_TYPE = TERMINAL_TYPES.VT220;
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// VT220 function key escape sequences (sent by client)
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
export const VT_KEYS = {
|
|
62
|
+
// F1-F5 (VT220 sends SS3 sequences for F1-F4)
|
|
63
|
+
F1: '\x1bOP',
|
|
64
|
+
F2: '\x1bOQ',
|
|
65
|
+
F3: '\x1bOR',
|
|
66
|
+
F4: '\x1bOS',
|
|
67
|
+
F5: '\x1b[15~',
|
|
68
|
+
// F6-F12
|
|
69
|
+
F6: '\x1b[17~',
|
|
70
|
+
F7: '\x1b[18~',
|
|
71
|
+
F8: '\x1b[19~',
|
|
72
|
+
F9: '\x1b[20~',
|
|
73
|
+
F10: '\x1b[21~',
|
|
74
|
+
F11: '\x1b[23~',
|
|
75
|
+
F12: '\x1b[24~',
|
|
76
|
+
// F13-F24 (shifted function keys on VT220)
|
|
77
|
+
F13: '\x1b[25~',
|
|
78
|
+
F14: '\x1b[26~',
|
|
79
|
+
F15: '\x1b[28~',
|
|
80
|
+
F16: '\x1b[29~',
|
|
81
|
+
F17: '\x1b[31~',
|
|
82
|
+
F18: '\x1b[32~',
|
|
83
|
+
F19: '\x1b[33~',
|
|
84
|
+
F20: '\x1b[34~',
|
|
85
|
+
// Arrow keys
|
|
86
|
+
UP: '\x1b[A',
|
|
87
|
+
DOWN: '\x1b[B',
|
|
88
|
+
RIGHT: '\x1b[C',
|
|
89
|
+
LEFT: '\x1b[D',
|
|
90
|
+
// Editing keys
|
|
91
|
+
HOME: '\x1b[1~',
|
|
92
|
+
INSERT: '\x1b[2~',
|
|
93
|
+
DELETE: '\x1b[3~',
|
|
94
|
+
END: '\x1b[4~',
|
|
95
|
+
PAGEUP: '\x1b[5~',
|
|
96
|
+
PAGEDOWN: '\x1b[6~',
|
|
97
|
+
// Special keys
|
|
98
|
+
ENTER: '\r',
|
|
99
|
+
TAB: '\t',
|
|
100
|
+
BACKSPACE: '\x7f',
|
|
101
|
+
ESCAPE: '\x1b',
|
|
102
|
+
};
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// SGR (Select Graphic Rendition) attribute codes
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
export const SGR = {
|
|
107
|
+
RESET: 0,
|
|
108
|
+
BOLD: 1,
|
|
109
|
+
DIM: 2,
|
|
110
|
+
ITALIC: 3,
|
|
111
|
+
UNDERLINE: 4,
|
|
112
|
+
BLINK: 5,
|
|
113
|
+
RAPID_BLINK: 6,
|
|
114
|
+
REVERSE: 7,
|
|
115
|
+
HIDDEN: 8,
|
|
116
|
+
STRIKETHROUGH: 9,
|
|
117
|
+
// Turn off attributes
|
|
118
|
+
NORMAL_INTENSITY: 22,
|
|
119
|
+
NO_ITALIC: 23,
|
|
120
|
+
NO_UNDERLINE: 24,
|
|
121
|
+
NO_BLINK: 25,
|
|
122
|
+
NO_REVERSE: 27,
|
|
123
|
+
NO_HIDDEN: 28,
|
|
124
|
+
NO_STRIKETHROUGH: 29,
|
|
125
|
+
// Foreground colors
|
|
126
|
+
FG_BLACK: 30,
|
|
127
|
+
FG_RED: 31,
|
|
128
|
+
FG_GREEN: 32,
|
|
129
|
+
FG_YELLOW: 33,
|
|
130
|
+
FG_BLUE: 34,
|
|
131
|
+
FG_MAGENTA: 35,
|
|
132
|
+
FG_CYAN: 36,
|
|
133
|
+
FG_WHITE: 37,
|
|
134
|
+
FG_DEFAULT: 39,
|
|
135
|
+
// Background colors
|
|
136
|
+
BG_BLACK: 40,
|
|
137
|
+
BG_RED: 41,
|
|
138
|
+
BG_GREEN: 42,
|
|
139
|
+
BG_YELLOW: 43,
|
|
140
|
+
BG_BLUE: 44,
|
|
141
|
+
BG_MAGENTA: 45,
|
|
142
|
+
BG_CYAN: 46,
|
|
143
|
+
BG_WHITE: 47,
|
|
144
|
+
BG_DEFAULT: 49,
|
|
145
|
+
// Bright foreground colors
|
|
146
|
+
FG_BRIGHT_BLACK: 90,
|
|
147
|
+
FG_BRIGHT_RED: 91,
|
|
148
|
+
FG_BRIGHT_GREEN: 92,
|
|
149
|
+
FG_BRIGHT_YELLOW: 93,
|
|
150
|
+
FG_BRIGHT_BLUE: 94,
|
|
151
|
+
FG_BRIGHT_MAGENTA: 95,
|
|
152
|
+
FG_BRIGHT_CYAN: 96,
|
|
153
|
+
FG_BRIGHT_WHITE: 97,
|
|
154
|
+
// Bright background colors
|
|
155
|
+
BG_BRIGHT_BLACK: 100,
|
|
156
|
+
BG_BRIGHT_RED: 101,
|
|
157
|
+
BG_BRIGHT_GREEN: 102,
|
|
158
|
+
BG_BRIGHT_YELLOW: 103,
|
|
159
|
+
BG_BRIGHT_BLUE: 104,
|
|
160
|
+
BG_BRIGHT_MAGENTA: 105,
|
|
161
|
+
BG_BRIGHT_CYAN: 106,
|
|
162
|
+
BG_BRIGHT_WHITE: 107,
|
|
163
|
+
};
|