Add attach addon

This commit is contained in:
Daniel Imms
2019-05-31 19:58:12 -07:00
parent 1cde8a3167
commit a1a63f99ff
15 changed files with 4112 additions and 16 deletions
@@ -0,0 +1,98 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
import WebSocket = require('ws');
const APP = 'http://127.0.0.1:3000/test';
let browser: puppeteer.Browser;
let page: puppeteer.Page;
const width = 800;
const height = 600;
describe.only('API Integration Tests', () => {
before(async function(): Promise<any> {
this.timeout(10000);
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80,
args: [`--window-size=${width},${height}`]
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
});
after(async () => {
await browser.close();
});
beforeEach(async function(): Promise<any> {
this.timeout(5000);
await page.goto(APP);
});
it('string', async function(): Promise<any> {
this.timeout(20000);
await openTerminal({ rendererType: 'dom' });
const port = 8080;
const server = new WebSocket.Server({ port });
server.on('connection', socket => socket.send('foo'));
await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo');
server.close();
});
it('utf8', async function(): Promise<any> {
this.timeout(20000);
await openTerminal({ rendererType: 'dom' });
const port = 8080;
const server = new WebSocket.Server({ port });
const data = new Uint8Array([102, 111, 111]);
server.on('connection', socket => socket.send(data));
await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}'), { inputUtf8: true }))`);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo');
server.close();
});
});
// async function testHostName(hostname: string): Promise<void> {
// await openTerminal({ rendererType: 'dom' });
// await page.evaluate(`window.term.loadAddon(new window.WebLinksAddon())`);
// await page.evaluate(`
// window.term.writeln(' http://${hostname} ');
// window.term.writeln(' http://${hostname}/a~b#c~d?e~f ');
// window.term.writeln(' http://${hostname}/colon:test ');
// window.term.writeln(' http://${hostname}/colon:test: ');
// window.term.writeln('"http://${hostname}/"');
// window.term.writeln('\\'http://${hostname}/\\'');
// window.term.writeln('http://${hostname}/subpath/+/id');
// `);
// assert.equal(await getLinkAtCell(3, 1), `http://${hostname}`);
// assert.equal(await getLinkAtCell(3, 2), `http://${hostname}/a~b#c~d?e~f`);
// assert.equal(await getLinkAtCell(3, 3), `http://${hostname}/colon:test`);
// assert.equal(await getLinkAtCell(3, 4), `http://${hostname}/colon:test`);
// assert.equal(await getLinkAtCell(2, 5), `http://${hostname}/`);
// assert.equal(await getLinkAtCell(2, 6), `http://${hostname}/`);
// assert.equal(await getLinkAtCell(1, 7), `http://${hostname}/subpath/+/id`);
// }
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
await page.evaluate(`window.term = new Terminal(${JSON.stringify(options)})`);
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
if (options.rendererType === 'dom') {
await page.waitForSelector('.xterm-rows');
} else {
await page.waitForSelector('.xterm-text-layer');
}
}
// async function getLinkAtCell(col: number, row: number): Promise<string> {
// const rowSelector = `.xterm-rows > :nth-child(${row})`;
// await page.hover(`${rowSelector} > :nth-child(${col})`);
// return await page.evaluate(`Array.prototype.reduce.call(document.querySelectorAll('${rowSelector} > span[style]'), (a, b) => a + b.textContent, '');`);
// }
@@ -0,0 +1,85 @@
/**
* Copyright (c) 2014, 2019 The xterm.js authors. All rights reserved.
* @license MIT
*
* Implements the attach method, that attaches the terminal to a WebSocket stream.
*/
import { Terminal, IDisposable } from 'xterm';
interface IAttachOptions {
bidirectional?: boolean;
inputUtf8?: boolean;
}
// TODO: This is temporary, link to xterm when the new version is published
export interface ITerminalAddon {
activate(terminal: Terminal): void;
dispose(): void;
}
// TODO: To be removed once UTF8 PR is in xterm.js package.
interface INewTerminal extends Terminal {
writeUtf8(data: Uint8Array): void;
}
export class AttachAddon implements ITerminalAddon {
private _socket: WebSocket;
private _bidirectional: boolean;
private _utf8: boolean;
private _disposables: IDisposable[] = [];
constructor(socket: WebSocket, options?: IAttachOptions) {
this._socket = socket;
// always set binary type to arraybuffer, we do not handle blobs
this._socket.binaryType = 'arraybuffer';
this._bidirectional = (options && options.bidirectional === false) ? false : true;
this._utf8 = !!(options && options.inputUtf8);
}
public activate(terminal: Terminal): void {
if (this._utf8) {
this._disposables.push(addSocketListener(this._socket, 'message',
(ev: MessageEvent | Event | CloseEvent) => (terminal as INewTerminal).writeUtf8(new Uint8Array((ev as any).data as ArrayBuffer))));
} else {
this._disposables.push(addSocketListener(this._socket, 'message',
(ev: MessageEvent | Event | CloseEvent) => (terminal as INewTerminal).write((ev as any).data as string)));
}
if (this._bidirectional) {
this._disposables.push(terminal.addDisposableListener('data', data => this._sendData(data)));
}
this._disposables.push(addSocketListener(this._socket, 'close', () => this.dispose()));
this._disposables.push(addSocketListener(this._socket, 'error', () => this.dispose()));
}
public dispose(): void {
this._disposables.forEach(d => d.dispose());
}
private _sendData(data: string): void {
// TODO: do something better than just swallowing
// the data if the socket is not in a working condition
if (this._socket.readyState !== 1) {
return;
}
this._socket.send(data);
}
}
function addSocketListener(socket: WebSocket, type: string, handler: (this: WebSocket, ev: MessageEvent | Event | CloseEvent) => any): IDisposable {
socket.addEventListener(type, handler);
return {
dispose: () => {
if (!handler) {
// Already disposed
return;
}
socket.removeEventListener(type, handler);
}
};
}
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"lib": [
"dom",
"es5",
"es2015.promise"
],
"rootDir": ".",
"outDir": "../lib",
"sourceMap": true,
"removeComments": true,
"strict": true,
"noImplicitAny": true
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
]
}