diff --git a/.vscode/settings.json b/.vscode/settings.json index 07d643c6..af3cc208 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,7 @@ { + "files.associations": { + ".eslintrc.json.typings": "jsonc" + }, "typescript.preferences.importModuleSpecifier": "non-relative", "typescript.preferences.quoteStyle": "single", "mochaExplorer.envPath": ".mocha.env", diff --git a/demo/client.ts b/demo/client.ts index 4c63e90f..d0b79c68 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -59,7 +59,7 @@ let socket; let pid; let autoResize: boolean = true; -type AddonType = 'attach' | 'canvas' | 'fit' | 'image' | 'search' | 'serialize' | 'unicode11' | 'web-links' | 'webgl' | 'ligatures'; +type AddonType = 'attach' | 'canvas' | 'fit' | 'image' | 'search' | 'serialize' | 'unicode11' | 'webLinks' | 'webgl' | 'ligatures'; interface IDemoAddon { name: T; @@ -71,7 +71,7 @@ interface IDemoAddon { T extends 'image' ? typeof ImageAddon : T extends 'search' ? typeof SearchAddon : T extends 'serialize' ? typeof SerializeAddon : - T extends 'web-links' ? typeof WebLinksAddon : + T extends 'webLinks' ? typeof WebLinksAddon : T extends 'unicode11' ? typeof Unicode11Addon : T extends 'ligatures' ? typeof LigaturesAddon : typeof WebglAddon @@ -83,7 +83,7 @@ interface IDemoAddon { T extends 'image' ? ImageAddon : T extends 'search' ? SearchAddon : T extends 'serialize' ? SerializeAddon : - T extends 'web-links' ? WebLinksAddon : + T extends 'webLinks' ? WebLinksAddon : T extends 'webgl' ? WebglAddon : T extends 'unicode11' ? typeof Unicode11Addon : T extends 'ligatures' ? typeof LigaturesAddon : @@ -98,7 +98,7 @@ const addons: { [T in AddonType]: IDemoAddon } = { image: { name: 'image', ctor: ImageAddon, canChange: true }, search: { name: 'search', ctor: SearchAddon, canChange: true }, serialize: { name: 'serialize', ctor: SerializeAddon, canChange: true }, - 'web-links': { name: 'web-links', ctor: WebLinksAddon, canChange: true }, + webLinks: { name: 'webLinks', ctor: WebLinksAddon, canChange: true }, webgl: { name: 'webgl', ctor: WebglAddon, canChange: true }, unicode11: { name: 'unicode11', ctor: Unicode11Addon, canChange: true }, ligatures: { name: 'ligatures', ctor: LigaturesAddon, canChange: true } @@ -170,7 +170,7 @@ const disposeRecreateButtonHandler: () => void = () => { addons.serialize.instance = undefined; addons.unicode11.instance = undefined; addons.ligatures.instance = undefined; - addons['web-links'].instance = undefined; + addons.webLinks.instance = undefined; addons.webgl.instance = undefined; document.getElementById('dispose').innerHTML = 'Recreate Terminal'; } else { @@ -272,13 +272,13 @@ function createTerminal(): void { } catch (e) { console.warn(e); } - addons['web-links'].instance = new WebLinksAddon(); + addons.webLinks.instance = new WebLinksAddon(); typedTerm.loadAddon(addons.fit.instance); typedTerm.loadAddon(addons.image.instance); typedTerm.loadAddon(addons.search.instance); typedTerm.loadAddon(addons.serialize.instance); typedTerm.loadAddon(addons.unicode11.instance); - typedTerm.loadAddon(addons['web-links'].instance); + typedTerm.loadAddon(addons.webLinks.instance); window.term = term; // Expose `term` to window for debugging purposes term.onResize((size: { cols: number, rows: number }) => { @@ -418,10 +418,13 @@ function initOptions(term: TerminalType): void { 'cancelEvents', 'convertEol', 'termName', - 'cols', 'rows', // subsumed by "size" (cols_rows) option + 'cols', 'rows', // subsumed by "size" (colsRows) option // Complex option + 'linkHandler', + 'logger', 'theme', - 'windowOptions' + 'windowOptions', + 'windowsPty' ]; const stringOptions = { cursorStyle: ['block', 'underline', 'bar'], @@ -433,7 +436,7 @@ function initOptions(term: TerminalType): void { logLevel: ['trace', 'debug', 'info', 'warn', 'error', 'off'], theme: ['default', 'xtermjs', 'sapphire', 'light'], wordSeparator: null, - cols_rows: null + colsRows: null }; const options = Object.getOwnPropertyNames(term.options); const booleanOptions = []; @@ -466,7 +469,7 @@ function initOptions(term: TerminalType): void { }); html += '
'; Object.keys(stringOptions).forEach(o => { - if (o === 'cols_rows') { + if (o === 'colsRows') { html += `
`; } else if (stringOptions[o]) { const selectedOption = o === 'theme' ? 'xtermjs' : term.options[o]; @@ -511,8 +514,8 @@ function initOptions(term: TerminalType): void { addDomListener(input, 'change', () => { console.log('change', o, input.value); let value: any = input.value; - if (o === 'cols_rows') { - let m = input.value.match(/^([0-9]+)x([0-9]+)$/); + if (o === 'colsRows') { + const m = input.value.match(/^([0-9]+)x([0-9]+)$/); if (m) { autoResize = false; term.resize(parseInt(m[1]), parseInt(m[2])); @@ -1275,29 +1278,29 @@ function initImageAddonExposed(): void { const ctorOptionsElement = document.querySelector('#image-options'); ctorOptionsElement.value = JSON.stringify(DEFAULT_OPTIONS, null, 2); - const sixel_demo = (url: string) => () => fetch(url) + const sixelDemo = (url: string) => () => fetch(url) .then(resp => resp.arrayBuffer()) .then(buffer => { term.write('\r\n'); term.write(new Uint8Array(buffer)); }); - const iip_demo = (url: string) => () => fetch(url) - .then(resp => resp.arrayBuffer()) - .then(buffer => { - const data = new Uint8Array(buffer); - let sdata = ''; - for (let i = 0; i < data.length; ++i) sdata += String.fromCharCode(data[i]); - term.write('\r\n'); - term.write(`\x1b]1337;File=inline=1;size=${data.length}:${btoa(sdata)}\x1b\\`); - }); + const iipDemo = (url: string) => () => fetch(url) + .then(resp => resp.arrayBuffer()) + .then(buffer => { + const data = new Uint8Array(buffer); + let sdata = ''; + for (let i = 0; i < data.length; ++i) sdata += String.fromCharCode(data[i]); + term.write('\r\n'); + term.write(`\x1b]1337;File=inline=1;size=${data.length}:${btoa(sdata)}\x1b\\`); + }); document.getElementById('image-demo1').addEventListener('click', - sixel_demo('https://raw.githubusercontent.com/saitoha/libsixel/master/images/snake.six')); + sixelDemo('https://raw.githubusercontent.com/saitoha/libsixel/master/images/snake.six')); document.getElementById('image-demo2').addEventListener('click', - sixel_demo('https://raw.githubusercontent.com/jerch/node-sixel/master/testfiles/test2.sixel')); + sixelDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/testfiles/test2.sixel')); document.getElementById('image-demo3').addEventListener('click', - iip_demo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png')); + iipDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png')); // demo for image retrieval API term.element.addEventListener('click', (ev: MouseEvent) => { diff --git a/demo/server.js b/demo/server.js index f477ae79..90ed2842 100644 --- a/demo/server.js +++ b/demo/server.js @@ -4,21 +4,23 @@ * demo to the public as is would introduce security risks for the host. **/ -var express = require('express'); -var expressWs = require('express-ws'); -var os = require('os'); -var pty = require('node-pty'); +// @ts-check -// Whether to use binary transport. +const express = require('express'); +const expressWs = require('express-ws'); +const os = require('os'); +const pty = require('node-pty'); + +/** Whether to use binary transport. */ const USE_BINARY = os.platform() !== "win32"; function startServer() { - var app = express(); - expressWs(app); + const app = express(); + const appWs = expressWs(app).app; - var terminals = {}, - unsentOutput = {}, - temporaryDisposable = {}; + const terminals = {}; + const unsentOutput = {}; + const temporaryDisposable = {}; app.use('/xterm.css', express.static(__dirname + '/../css/xterm.css')); app.get('/logo.png', (req, res) => { @@ -41,18 +43,30 @@ function startServer() { app.use('/src', express.static(__dirname + '/src')); app.post('/terminals', (req, res) => { - const env = Object.assign({}, process.env); + /** @type {{ [key: string]: string }} */ + const env = {}; + for (const k of Object.keys(process.env)) { + const v = process.env[k]; + if (v) { + env[k] = v; + } + } + // const env = Object.assign({}, process.env); env['COLORTERM'] = 'truecolor'; - var cols = parseInt(req.query.cols), - rows = parseInt(req.query.rows), - term = pty.spawn(process.platform === 'win32' ? 'pwsh.exe' : 'bash', [], { - name: 'xterm-256color', - cols: cols || 80, - rows: rows || 24, - cwd: process.platform === 'win32' ? undefined : env.PWD, - env: env, - encoding: USE_BINARY ? null : 'utf8' - }); + if (typeof req.query.cols !== 'string' || typeof req.query.rows !== 'string') { + console.error({ req }); + throw new Error('Unexpected query args'); + } + const cols = parseInt(req.query.cols); + const rows = parseInt(req.query.rows); + const term = pty.spawn(process.platform === 'win32' ? 'pwsh.exe' : 'bash', [], { + name: 'xterm-256color', + cols: cols ?? 80, + rows: rows ?? 24, + cwd: process.platform === 'win32' ? undefined : env.PWD, + env, + encoding: USE_BINARY ? null : 'utf8' + }); console.log('Created terminal with PID: ' + term.pid); terminals[term.pid] = term; @@ -65,18 +79,22 @@ function startServer() { }); app.post('/terminals/:pid/size', (req, res) => { - var pid = parseInt(req.params.pid), - cols = parseInt(req.query.cols), - rows = parseInt(req.query.rows), - term = terminals[pid]; + if (typeof req.query.cols !== 'string' || typeof req.query.rows !== 'string') { + console.error({ req }); + throw new Error('Unexpected query args'); + } + const pid = parseInt(req.params.pid); + const cols = parseInt(req.query.cols); + const rows = parseInt(req.query.rows); + const term = terminals[pid]; term.resize(cols, rows); console.log('Resized terminal ' + pid + ' to ' + cols + ' cols and ' + rows + ' rows.'); res.end(); }); - app.ws('/terminals/:pid', function (ws, req) { - var term = terminals[parseInt(req.params.pid)]; + appWs.ws('/terminals/:pid', function (ws, req) { + const term = terminals[parseInt(req.params.pid)]; console.log('Connected to terminal ' + term.pid); temporaryDisposable[term.pid].dispose(); delete temporaryDisposable[term.pid]; @@ -160,11 +178,11 @@ function startServer() { }); }); - var port = process.env.PORT || 3000, - host = os.platform() === 'win32' ? '127.0.0.1' : '0.0.0.0'; + const port = parseInt(process.env.PORT ?? '3000'); + const host = os.platform() === 'win32' ? '127.0.0.1' : '0.0.0.0'; console.log('App listening to http://127.0.0.1:' + port); - app.listen(port, host); + app.listen(port, host, 0); } module.exports = startServer; diff --git a/demo/start.js b/demo/start.js index b40b9bc3..7349f36d 100644 --- a/demo/start.js +++ b/demo/start.js @@ -1,10 +1,10 @@ /** * Copyright (c) 2018 The xterm.js authors. All rights reserved. * @license MIT - * - * This file is the entry point for browserify. */ +// @ts-check + const path = require('path'); const webpack = require('webpack'); const startServer = require('./server.js'); @@ -20,6 +20,8 @@ startServer(); * For production builds see `webpack.config.js` in the root directory. If that is built the demo * can use that by switching out which `Terminal` is imported in `client.ts`, this is useful for * validating that the packaged version works correctly. + * + * @type {import('webpack').Configuration} */ const clientConfig = { entry: path.resolve(__dirname, 'client.ts'), @@ -69,12 +71,13 @@ const clientConfig = { const compiler = webpack(clientConfig); compiler.watch({ - // Example watchOptions aggregateTimeout: 300, poll: undefined }, (err, stats) => { - // Print watch/build result here... - console.log(stats.toString({ + if (err) { + console.error(err); + } + console.log(stats?.toString({ colors: true })); }); diff --git a/package.json b/package.json index be507b6e..de823832 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,8 @@ "@types/chai": "^4.2.22", "@types/debug": "^4.1.7", "@types/deep-equal": "^1.0.1", + "@types/express": "4", + "@types/express-ws": "^3.0.1", "@types/glob": "^7.2.0", "@types/jsdom": "^16.2.13", "@types/mocha": "^9.0.0", diff --git a/webpack.config.headless.js b/webpack.config.headless.js index d5bb97b7..9e9099cd 100644 --- a/webpack.config.headless.js +++ b/webpack.config.headless.js @@ -10,8 +10,10 @@ const path = require('path'); * from tsc (via `yarn watch` or `yarn prebuild`) which are put into `out/` and webpacks them into a * production mode umd library module in `lib-headless/`. The aliases are used fix up the absolute * paths output by tsc (because of `baseUrl` and `paths` in `tsconfig.json`. + * + * @type {import('webpack').Configuration} */ -module.exports = { +const config = { entry: './out/headless/public/Terminal.js', devtool: 'source-map', module: { @@ -41,3 +43,4 @@ module.exports = { }, mode: 'production' }; +module.exports = config; diff --git a/webpack.config.js b/webpack.config.js index 4f9087b5..ca7c059a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,6 +3,8 @@ * @license MIT */ +// @ts-check + const path = require('path'); /** @@ -10,8 +12,10 @@ const path = require('path'); * (via `yarn watch` or `yarn prebuild`) which are put into `out/` and webpacks them into a * production mode umd library module in `lib/`. The aliases are used fix up the absolute paths * output by tsc (because of `baseUrl` and `paths` in `tsconfig.json`. + * + * @type {import('webpack').Configuration} */ -module.exports = { +const config = { entry: './out/browser/public/Terminal.js', devtool: 'source-map', module: { @@ -39,3 +43,4 @@ module.exports = { }, mode: 'production' }; +module.exports = config; diff --git a/yarn.lock b/yarn.lock index cde76d90..4b3a34ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -357,6 +357,14 @@ resolved "https://registry.yarnpkg.com/@types/app-root-path/-/app-root-path-1.2.5.tgz#71b6b3ad55061ad02e4a75e909b0c5fe776ae12c" integrity sha512-uJsNeY7Jwci2yDpjx0b99Vb7KOxAI7kgz7L7a19bXZMRFEhGSj0SZkGYg9nGgq+Zrp9nzEe+ceZRY68yIKqA5Q== +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + "@types/chai@^4.2.22": version "4.3.5" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.5.tgz#ae69bcbb1bebb68c4ac0b11e9d8ed04526b3562b" @@ -367,6 +375,13 @@ resolved "https://registry.yarnpkg.com/@types/cli-table/-/cli-table-0.3.1.tgz#a0ae06290284f7abebb90a2ddc0187de6d22e963" integrity sha512-m3+6WWfSSl6zqoXy8uQQifbgqV7Gt6fsyWnHLgUWVtJQk75+OfUB+edSZ52YDj7leSiZtX7w1/E4w2x/Hb0orA== +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + "@types/debug@^4.1.7": version "4.1.8" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.8.tgz#cef723a5d0a90990313faec2d1e22aee5eecb317" @@ -400,6 +415,35 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": + version "4.17.36" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.36.tgz#baa9022119bdc05a4adfe740ffc97b5f9360e545" + integrity sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express-ws@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/express-ws/-/express-ws-3.0.1.tgz#6fbf5dfdbeedd16479ccbeecbca63c14be26612e" + integrity sha512-VguRXzcpPBF0IggIGpUoM65cZJDfMQxoc6dKoCz1yLzcwcXW7ft60yhq3ygKhyEhEIQFtLrWjyz4AJ1qjmzCFw== + dependencies: + "@types/express" "*" + "@types/express-serve-static-core" "*" + "@types/ws" "*" + +"@types/express@*", "@types/express@4": + version "4.17.17" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4" + integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/glob@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" @@ -408,6 +452,11 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/http-errors@*": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.1.tgz#20172f9578b225f6c7da63446f56d4ce108d5a65" + integrity sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ== + "@types/jsdom@^16.2.13": version "16.2.15" resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-16.2.15.tgz#6c09990ec43b054e49636cba4d11d54367fc90d6" @@ -429,6 +478,16 @@ dependencies: decimal.js "^10.0.0" +"@types/mime@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== + +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + "@types/minimatch@*": version "5.1.2" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" @@ -476,11 +535,38 @@ dependencies: "@types/node" "*" +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + "@types/semver@^7.5.0": version "7.5.0" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== +"@types/send@*": + version "0.17.1" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.1.tgz#ed4932b8a2a805f1fe362a70f4e62d0ac994e301" + integrity sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@*": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.2.tgz#3e5419ecd1e40e7405d34093f10befb43f63381a" + integrity sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw== + dependencies: + "@types/http-errors" "*" + "@types/mime" "*" + "@types/node" "*" + "@types/tough-cookie@*": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397" @@ -500,7 +586,7 @@ tapable "^2.2.0" webpack "^5" -"@types/ws@^8.2.0": +"@types/ws@*", "@types/ws@^8.2.0": version "8.5.5" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.5.tgz#af587964aa06682702ee6dcbc7be41a80e4b28eb" integrity sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==