mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Add attach addon
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
lib
|
||||
node_modules
|
||||
test/dist
|
||||
@@ -0,0 +1,5 @@
|
||||
lib/**/*.js.map
|
||||
src/
|
||||
node_modules/
|
||||
tsconfig.json
|
||||
.editorconfig
|
||||
@@ -0,0 +1 @@
|
||||
--modules-folder "../../node_modules"
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "xterm-addon-attach",
|
||||
"version": "0.1.0-beta8",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
},
|
||||
"main": "lib/AttachAddon.js",
|
||||
"types": "typings/attach.d.ts",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"prepublish": "tsc -p src"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^3.10.0"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
|
||||
import { Terminal, ILinkMatcherOptions } from 'xterm';
|
||||
|
||||
// TODO: This is temporary, link to xterm when the new version is published
|
||||
export interface ITerminalAddon {
|
||||
activate(terminal: Terminal): void;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export interface IAttachOptions {
|
||||
/**
|
||||
* Whether input should be written to the backend. Defaults to `true`.
|
||||
*/
|
||||
bidirectional?: boolean,
|
||||
|
||||
/**
|
||||
* Whether to use UTF8 binary transport for incoming messages. Defaults to `false`.
|
||||
* Note: This must be in line with the server side of the websocket.
|
||||
* Always send string messages from the backend if this options is false,
|
||||
* otherwise always binary UTF8 data.
|
||||
*/
|
||||
inputUtf8?: boolean
|
||||
}
|
||||
|
||||
export class AttachAddon implements ITerminalAddon {
|
||||
constructor(socket: WebSocket, options?: IAttachOptions);
|
||||
public activate(terminal: Terminal): void;
|
||||
public dispose(): void;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,9 +9,7 @@
|
||||
"types": "typings/web-links.d.ts",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"watch": "tsc -w -p src",
|
||||
"prepublish": "tsc -p src",
|
||||
"test": "mocha \"lib/**/*.test.js\""
|
||||
"prepublish": "tsc -p src"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^3.13.0"
|
||||
|
||||
@@ -25,6 +25,7 @@ import { Terminal as TerminalType, ITerminalOptions } from 'xterm';
|
||||
export interface IWindowWithTerminal extends Window {
|
||||
term: TerminalType;
|
||||
Terminal?: typeof TerminalType;
|
||||
AttachAddon?: typeof AttachAddon;
|
||||
WebLinksAddon?: typeof WebLinksAddon;
|
||||
}
|
||||
declare let window: IWindowWithTerminal;
|
||||
@@ -75,6 +76,7 @@ const disposeRecreateButtonHandler = () => {
|
||||
|
||||
if (document.location.pathname === '/test') {
|
||||
window.Terminal = Terminal;
|
||||
window.AttachAddon = AttachAddon;
|
||||
window.WebLinksAddon = WebLinksAddon;
|
||||
} else {
|
||||
createTerminal();
|
||||
|
||||
+2
-1
@@ -6,7 +6,8 @@
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"xterm-addon-web-links": ["../addons/xterm-addon-web-links"]
|
||||
"xterm-addon-web-links": ["../addons/xterm-addon-web-links"],
|
||||
"xterm-addon-attach": ["../addons/xterm-addon-attach"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
|
||||
+2
-1
@@ -15,6 +15,7 @@
|
||||
"@types/puppeteer": "^1.12.4",
|
||||
"@types/utf8": "^2.1.6",
|
||||
"@types/webpack": "^4.4.11",
|
||||
"@types/ws": "^6.0.1",
|
||||
"chai": "3.5.0",
|
||||
"express": "4.13.4",
|
||||
"express-ws": "2.0.0-rc.1",
|
||||
@@ -31,7 +32,7 @@
|
||||
"utf8": "^3.0.0",
|
||||
"webpack": "^4.17.1",
|
||||
"webpack-cli": "^3.1.0",
|
||||
"xterm-addon-attach": "0.1.0-beta8",
|
||||
"ws": "^7.0.0",
|
||||
"xterm-addon-search": "0.1.0-beta4"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -108,6 +108,14 @@
|
||||
"@types/uglify-js" "*"
|
||||
source-map "^0.6.0"
|
||||
|
||||
"@types/ws@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.1.tgz#ca7a3f3756aa12f62a0a62145ed14c6db25d5a28"
|
||||
integrity sha512-EzH8k1gyZ4xih/MaZTXwT2xOkPiIMSrhQ9b8wrlX88L0T02eYsddatQlwVFlEPyEqV0ChpdpNnE51QPH6NVT4Q==
|
||||
dependencies:
|
||||
"@types/events" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@webassemblyjs/ast@1.5.13":
|
||||
version "1.5.13"
|
||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.5.13.tgz#81155a570bd5803a30ec31436bc2c9c0ede38f25"
|
||||
@@ -472,7 +480,7 @@ async-each@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
|
||||
integrity sha1-GdOGodntxufByF04iu28xW0zYC0=
|
||||
|
||||
async-limiter@~1.0.0:
|
||||
async-limiter@^1.0.0, async-limiter@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
|
||||
integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==
|
||||
@@ -4536,6 +4544,13 @@ ws@^6.1.0:
|
||||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
|
||||
ws@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.0.0.tgz#79351cbc3f784b3c20d0821baf4b4ff809ffbf51"
|
||||
integrity sha512-cknCal4k0EAOrh1SHHPPWWh4qm93g1IuGGGwBjWkXmCG7LsDtL8w9w+YVfaF+KSVwiHQKDIMsSLBVftKf9d1pg==
|
||||
dependencies:
|
||||
async-limiter "^1.0.0"
|
||||
|
||||
xml-name-validator@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
|
||||
@@ -4551,21 +4566,11 @@ xtend@^4.0.0, xtend@~4.0.1:
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||
integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
|
||||
|
||||
xterm-addon-attach@0.1.0-beta8:
|
||||
version "0.1.0-beta8"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-attach/-/xterm-addon-attach-0.1.0-beta8.tgz#e469ed9d6ab7e535d0a9ffae23ef4f2efe58163b"
|
||||
integrity sha512-HtQuwqnvcR+SwI9/JbBMd//Il+oEeo3rWrIucLLKHT8sB+OAOkdhmo5KIM/hhnovjI040WJ+tTHkDgPFwIJtmw==
|
||||
|
||||
xterm-addon-search@0.1.0-beta4:
|
||||
version "0.1.0-beta4"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.1.0-beta4.tgz#c73fe058c87f07eaae31baaa92976e927438a396"
|
||||
integrity sha512-tJgZ1VTRd/DOFUhSFZzybRF8SR1LCEXRYkw/mHzGV5Ba3zhqVdSkN/0J9sjOpX6u21buee2OmTiCMZxq80zfJg==
|
||||
|
||||
xterm-addon-web-links@0.1.0-beta6:
|
||||
version "0.1.0-beta6"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.1.0-beta6.tgz#9b4e862be8928ef455a667745bea479665db6c6b"
|
||||
integrity sha512-tkVU5wCfBFjXwfOvcbMHoLoMDANztkwSREiKyu2R059kEF+sP67Z33HzxVCXUWFuCmutcx40xR2O0BK68gXZlg==
|
||||
|
||||
"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
|
||||
|
||||
Reference in New Issue
Block a user