diff --git a/js/package-lock.json b/js/package-lock.json index 9913d76..11b9e1a 100644 --- a/js/package-lock.json +++ b/js/package-lock.json @@ -372,6 +372,15 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "crc-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", + "requires": { + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -533,6 +542,11 @@ "strip-final-newline": "^2.0.0" } }, + "exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -961,6 +975,11 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" }, + "printj": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==" + }, "process-nextick-args": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", @@ -1477,6 +1496,14 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true + }, + "zmodem.js": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/zmodem.js/-/zmodem.js-0.1.10.tgz", + "integrity": "sha512-Z1DWngunZ/j3BmIzSJpFZVNV73iHkj89rxXX4IciJdU9ga3nZ7rJ5LkfjV/QDsKhc7bazDWTTJCLJ+iRXD82dw==", + "requires": { + "crc-32": "^1.1.1" + } } } } diff --git a/js/package.json b/js/package.json index d3b59fa..bc41059 100644 --- a/js/package.json +++ b/js/package.json @@ -16,6 +16,7 @@ "xterm": "^4.11.0", "xterm-addon-fit": "^0.5.0", "xterm-addon-web-links": "^0.4.0", - "xterm-addon-webgl": "^0.10.0" + "xterm-addon-webgl": "^0.10.0", + "zmodem.js": "^0.1.10" } } diff --git a/js/src/xterm-addon-zmodem/package.json b/js/src/xterm-addon-zmodem/package.json new file mode 100644 index 0000000..218130a --- /dev/null +++ b/js/src/xterm-addon-zmodem/package.json @@ -0,0 +1,5 @@ +{ + "name": "xterm.zmodem", + "main": "zmodem.js", + "private": true +} diff --git a/js/src/xterm-addon-zmodem/tsconfig.json b/js/src/xterm-addon-zmodem/tsconfig.json new file mode 100644 index 0000000..7d821b7 --- /dev/null +++ b/js/src/xterm-addon-zmodem/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "lib": [ + "es5" + ], + "rootDir": ".", + "outDir": "../../../lib/addons/zmodem/", + "sourceMap": true, + "removeComments": true, + "declaration": true, + "types": [ + "../../node_modules/@types/mocha" + ] + }, + "include": [ + "**/*.ts", + "../../../typings/xterm.d.ts" + ] +} diff --git a/js/src/xterm-addon-zmodem/typings/xterm-addon-zmodem.d.ts b/js/src/xterm-addon-zmodem/typings/xterm-addon-zmodem.d.ts new file mode 100644 index 0000000..2dda172 --- /dev/null +++ b/js/src/xterm-addon-zmodem/typings/xterm-addon-zmodem.d.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + + +import { Terminal, ITerminalAddon } from 'xterm'; + +declare module 'xterm-addon-zmodem' { + /** + * An xterm.js addon that enables zmodem transfers. + */ + export class ZModemAddon implements ITerminalAddon { + /** + * Creates a new zmodem addon. + * @param handler The callback when the link is called. + */ + constructor(handler?: (event: MouseEvent, uri: string) => void); + + /** + * Activates the addon + * @param terminal The terminal the addon is being loaded in. + */ + public activate(terminal: Terminal): void; + + /** + * Disposes the addon. + */ + public dispose(): void; + } +} diff --git a/js/src/xterm-addon-zmodem/zmodem.test.ts b/js/src/xterm-addon-zmodem/zmodem.test.ts new file mode 100644 index 0000000..d0c7c5f --- /dev/null +++ b/js/src/xterm-addon-zmodem/zmodem.test.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; + +import * as zmodem from './zmodem'; + +class MockTerminal {} + +describe('zmodem addon', () => { + describe('apply', () => { + it('should do register the `zmodemAttach` method and `zmodemBrowser` attribute', () => { + zmodem.apply(MockTerminal); + assert.equal(typeof (MockTerminal).prototype.zmodemAttach, 'function'); + assert.equal(typeof (MockTerminal).prototype.zmodemBrowser, 'object'); + }); + }); +}); diff --git a/js/src/xterm-addon-zmodem/zmodem.ts b/js/src/xterm-addon-zmodem/zmodem.ts new file mode 100644 index 0000000..b1da59f --- /dev/null +++ b/js/src/xterm-addon-zmodem/zmodem.ts @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { Terminal, ITerminalAddon } from 'xterm'; + +declare module 'xterm-addon-zmodem' { + export class ZModemAddon implements ITerminalAddon { + /** + * + * Allow xterm.js to handle ZMODEM uploads and downloads. + * + * This addon is a wrapper around zmodem.js. It adds the following to the + * Terminal class: + * + * - function `zmodemAttach(, )` - creates a Zmodem.Sentry + * on the passed WebSocket object. The Object passed is optional and + * can contain: + * - noTerminalWriteOutsideSession: Suppress writes from the Sentry + * object to the Terminal while there is no active Session. This + * is necessary for compatibility with, for example, the + * `attach.js` addon. + * + * - event `zmodemDetect` - fired on Zmodem.Sentry’s `on_detect` callback. + * Passes the zmodem.js Detection object. + * + * - event `zmodemRetract` - fired on Zmodem.Sentry’s `on_retract` callback. + * + * You’ll need to provide logic to handle uploads and downloads. + * See zmodem.js’s documentation for more details. + * + * **IMPORTANT:** After you confirm() a zmodem.js Detection, if you have + * used the `attach` or `terminado` addons, you’ll need to suspend their + * operation for the duration of the ZMODEM session. (The demo does this + * via `detach()` and a re-`attach()`.) + */ + +let zmodem: any; + + export interface IZmodemOptions { + noTerminalWriteOutsideSession?: boolean; + } + + function zmodemAttach(ws: WebSocket, opts: IZmodemOptions = {}): void { + const term = this; + const senderFunc = (octets: ArrayLike) => ws.send(new Uint8Array(octets)); + + let zsentry: any; + + function shouldWrite(): boolean { + return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession; + } + + zsentry = new zmodem.Sentry({ + to_terminal: (octets: ArrayLike) => { + if (shouldWrite()) { + term.write( + String.fromCharCode.apply(String, octets) + ); + } + }, + sender: senderFunc, + on_retract: () => (term).emit('zmodemRetract'), + on_detect: (detection: any) => (term).emit('zmodemDetect', detection) + }); + + function handleWSMessage(evt: MessageEvent): void { + + // In testing with xterm.js’s demo the first message was + // always text even if the rest were binary. While that + // may be specific to xterm.js’s demo, ultimately we + // should reject anything that isn’t binary. + if (typeof evt.data === 'string') { + if (shouldWrite()) { + term.write(evt.data); + } + } + else { + zsentry.consume(evt.data); + } + } + + ws.binaryType = 'arraybuffer'; + ws.addEventListener('message', handleWSMessage); + } + + export function apply(terminalConstructor: typeof Terminal): void { + zmodem = (typeof window === 'object') ? (window).Zmodem : { Browser: null }; // Nullify browser for tests + + (terminalConstructor.prototype).zmodemAttach = zmodemAttach; + (terminalConstructor.prototype).zmodemBrowser = zmodem.Browser; + } +} + } diff --git a/js/src/xterm.ts b/js/src/xterm.ts index eeebca6..11f206f 100644 --- a/js/src/xterm.ts +++ b/js/src/xterm.ts @@ -2,6 +2,7 @@ import { Terminal, IDisposable } from "xterm"; import { FitAddon } from 'xterm-addon-fit'; import { WebLinksAddon } from 'xterm-addon-web-links'; import { WebglAddon } from 'xterm-addon-webgl'; +import { ZModemAddon } from 'xterm-addon-zmodem'; import { lib } from "libapps" export class Xterm {