27 changed files with 4101 additions and 2790 deletions
			
			
		@ -0,0 +1,9 @@ | 
				
			|||
{ | 
				
			|||
  "presets": [ | 
				
			|||
    "@babel/preset-typescript", | 
				
			|||
    ["@babel/env"] | 
				
			|||
  ], | 
				
			|||
  "plugins": [ | 
				
			|||
    "lodash" | 
				
			|||
  ] | 
				
			|||
} | 
				
			|||
@ -1,16 +1,32 @@ | 
				
			|||
module.exports = { | 
				
			|||
  parser: 'eslint-plugin-typescript/parser', | 
				
			|||
  plugins: ['typescript', 'prettier'], | 
				
			|||
  env: { | 
				
			|||
    es6: true, | 
				
			|||
    node: true, | 
				
			|||
    browser: true | 
				
			|||
    browser: true, | 
				
			|||
  }, | 
				
			|||
  root: true, | 
				
			|||
  extends: ["airbnb-base", "plugin:prettier/recommended"], | 
				
			|||
  extends: [ | 
				
			|||
    'airbnb-base', | 
				
			|||
    'plugin:typescript/recommended', | 
				
			|||
    'plugin:prettier/recommended', | 
				
			|||
  ], | 
				
			|||
  rules: { | 
				
			|||
    "linebreak-style": ["error", "unix"], | 
				
			|||
    "arrow-parens": ["error", "as-needed"], | 
				
			|||
    "no-param-reassign": ["error", { props: false }], | 
				
			|||
    "func-style": ["error", "declaration", { allowArrowFunctions: true }], | 
				
			|||
    "no-use-before-define": ["error", { functions: false }] | 
				
			|||
  } | 
				
			|||
    'typescript/indent': 'off', | 
				
			|||
    'linebreak-style': ['error', 'unix'], | 
				
			|||
    'arrow-parens': ['error', 'as-needed'], | 
				
			|||
    'no-param-reassign': ['error', { props: false }], | 
				
			|||
    'func-style': ['error', 'declaration', { allowArrowFunctions: true }], | 
				
			|||
    'no-use-before-define': ['error', { functions: false }], | 
				
			|||
    'typescript/no-use-before-define': ['error', { functions: false }], | 
				
			|||
  }, | 
				
			|||
  settings: { | 
				
			|||
    'import/resolver': { | 
				
			|||
      'typescript-eslint-parser': ['.ts', '.tsx'], | 
				
			|||
      node: { | 
				
			|||
        extensions: ['.ts', '.js'], | 
				
			|||
      }, | 
				
			|||
    }, | 
				
			|||
  }, | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
@ -1,12 +1,72 @@ | 
				
			|||
/* eslint-disable */ | 
				
			|||
require = require('@std/esm')(module, { | 
				
			|||
  cjs: 'true', | 
				
			|||
  esm: 'js', | 
				
			|||
}); | 
				
			|||
const wetty = require('./lib/index.mjs').default; | 
				
			|||
/* eslint-disable typescript/no-var-requires */ | 
				
			|||
 | 
				
			|||
const yargs = require('yargs'); | 
				
			|||
const wetty = require('./dist').default; | 
				
			|||
 | 
				
			|||
module.exports = wetty.wetty; | 
				
			|||
 | 
				
			|||
/** | 
				
			|||
 * Check if being run by cli or require | 
				
			|||
 */ | 
				
			|||
if (require.main === module) wetty.init(); | 
				
			|||
if (require.main === module) | 
				
			|||
  wetty.init( | 
				
			|||
    yargs | 
				
			|||
      .options({ | 
				
			|||
        sslkey: { | 
				
			|||
          demand: false, | 
				
			|||
          type: 'string', | 
				
			|||
          description: 'path to SSL key', | 
				
			|||
        }, | 
				
			|||
        sslcert: { | 
				
			|||
          demand: false, | 
				
			|||
          type: 'string', | 
				
			|||
          description: 'path to SSL certificate', | 
				
			|||
        }, | 
				
			|||
        sshhost: { | 
				
			|||
          demand: false, | 
				
			|||
          description: 'ssh server host', | 
				
			|||
          type: 'string', | 
				
			|||
          default: process.env.SSHHOST || 'localhost', | 
				
			|||
        }, | 
				
			|||
        sshport: { | 
				
			|||
          demand: false, | 
				
			|||
          description: 'ssh server port', | 
				
			|||
          type: 'number', | 
				
			|||
          default: parseInt(process.env.SSHPOST, 10) || 22, | 
				
			|||
        }, | 
				
			|||
        sshuser: { | 
				
			|||
          demand: false, | 
				
			|||
          description: 'ssh user', | 
				
			|||
          type: 'string', | 
				
			|||
          default: process.env.SSHUSER || '', | 
				
			|||
        }, | 
				
			|||
        sshauth: { | 
				
			|||
          demand: false, | 
				
			|||
          description: | 
				
			|||
            'defaults to "password", you can use "publickey,password" instead', | 
				
			|||
          type: 'string', | 
				
			|||
          default: process.env.SSHAUTH || 'password', | 
				
			|||
        }, | 
				
			|||
        base: { | 
				
			|||
          demand: false, | 
				
			|||
          alias: 'b', | 
				
			|||
          description: 'base path to wetty', | 
				
			|||
          type: 'string', | 
				
			|||
          default: process.env.BASE || '/wetty/', | 
				
			|||
        }, | 
				
			|||
        port: { | 
				
			|||
          demand: false, | 
				
			|||
          alias: 'p', | 
				
			|||
          description: 'wetty listen port', | 
				
			|||
          type: 'number', | 
				
			|||
          default: parseInt(process.env.PORT, 10) || 3000, | 
				
			|||
        }, | 
				
			|||
        help: { | 
				
			|||
          demand: false, | 
				
			|||
          alias: 'h', | 
				
			|||
          type: 'boolean', | 
				
			|||
          description: 'Print help message', | 
				
			|||
        }, | 
				
			|||
      }) | 
				
			|||
      .boolean('allow_discovery').argv | 
				
			|||
  ); | 
				
			|||
 | 
				
			|||
@ -1,30 +0,0 @@ | 
				
			|||
const localhost = host => | 
				
			|||
  process.getuid() === 0 && | 
				
			|||
  (host === 'localhost' || host === '0.0.0.0' || host === '127.0.0.1'); | 
				
			|||
 | 
				
			|||
export default ( | 
				
			|||
  { request: { headers }, client: { conn } }, | 
				
			|||
  { user, host, port, auth } | 
				
			|||
) => ({ | 
				
			|||
  args: localhost(host) | 
				
			|||
    ? ['login', '-h', conn.remoteAddress.split(':')[3]] | 
				
			|||
    : [ | 
				
			|||
        'ssh', | 
				
			|||
        address(headers, user, host), | 
				
			|||
        '-p', | 
				
			|||
        port, | 
				
			|||
        '-o', | 
				
			|||
        `PreferredAuthentications=${auth}`, | 
				
			|||
      ], | 
				
			|||
  user: | 
				
			|||
    localhost(host) || | 
				
			|||
    user !== '' || | 
				
			|||
    user.includes('@') || | 
				
			|||
    address(headers, user, host).includes('@'), | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
function address(headers, user, host) { | 
				
			|||
  const match = headers.referer.match('.+/ssh/([^/]+)$'); | 
				
			|||
  const fallback = user ? `${user}@${host}` : host; | 
				
			|||
  return match ? `${match[1]}@${host}` : fallback; | 
				
			|||
} | 
				
			|||
@ -1,100 +0,0 @@ | 
				
			|||
import optimist from 'optimist'; | 
				
			|||
import logger from './logger.mjs'; | 
				
			|||
import wetty from './emitter.mjs'; | 
				
			|||
 | 
				
			|||
const opts = optimist | 
				
			|||
  .options({ | 
				
			|||
    sslkey: { | 
				
			|||
      demand: false, | 
				
			|||
      description: 'path to SSL key', | 
				
			|||
    }, | 
				
			|||
    sslcert: { | 
				
			|||
      demand: false, | 
				
			|||
      description: 'path to SSL certificate', | 
				
			|||
    }, | 
				
			|||
    sshhost: { | 
				
			|||
      demand: false, | 
				
			|||
      description: 'ssh server host', | 
				
			|||
    }, | 
				
			|||
    sshport: { | 
				
			|||
      demand: false, | 
				
			|||
      description: 'ssh server port', | 
				
			|||
    }, | 
				
			|||
    sshuser: { | 
				
			|||
      demand: false, | 
				
			|||
      description: 'ssh user', | 
				
			|||
    }, | 
				
			|||
    sshauth: { | 
				
			|||
      demand: false, | 
				
			|||
      description: | 
				
			|||
        'defaults to "password", you can use "publickey,password" instead', | 
				
			|||
    }, | 
				
			|||
    base: { | 
				
			|||
      demand: false, | 
				
			|||
      alias: 'b', | 
				
			|||
      description: 'base path to wetty', | 
				
			|||
    }, | 
				
			|||
    port: { | 
				
			|||
      demand: false, | 
				
			|||
      alias: 'p', | 
				
			|||
      description: 'wetty listen port', | 
				
			|||
    }, | 
				
			|||
    help: { | 
				
			|||
      demand: false, | 
				
			|||
      alias: 'h', | 
				
			|||
      description: 'Print help message', | 
				
			|||
    }, | 
				
			|||
  }) | 
				
			|||
  .boolean('allow_discovery').argv; | 
				
			|||
 | 
				
			|||
export default class { | 
				
			|||
  static start({ | 
				
			|||
    sshuser = process.env.SSHUSER || '', | 
				
			|||
    sshhost = process.env.SSHHOST || 'localhost', | 
				
			|||
    sshauth = process.env.SSHAUTH || 'password', | 
				
			|||
    sshport = process.env.SSHPOST || 22, | 
				
			|||
    base = process.env.BASE || '/wetty/', | 
				
			|||
    port = process.env.PORT || 3000, | 
				
			|||
    sslkey, | 
				
			|||
    sslcert, | 
				
			|||
  }) { | 
				
			|||
    wetty | 
				
			|||
      .on('exit', ({ code, msg }) => { | 
				
			|||
        logger.info(`Exit with code: ${code} ${msg}`); | 
				
			|||
      }) | 
				
			|||
      .on('disconnect', () => { | 
				
			|||
        logger.info('disconnect'); | 
				
			|||
      }) | 
				
			|||
      .on('spawn', ({ msg }) => logger.info(msg)) | 
				
			|||
      .on('connection', ({ msg, date }) => logger.info(`${date} ${msg}`)) | 
				
			|||
      .on('server', ({ msg }) => logger.info(msg)) | 
				
			|||
      .on('debug', msg => logger.debug(msg)); | 
				
			|||
    return wetty.start( | 
				
			|||
      { | 
				
			|||
        user: sshuser, | 
				
			|||
        host: sshhost, | 
				
			|||
        auth: sshauth, | 
				
			|||
        port: sshport, | 
				
			|||
      }, | 
				
			|||
      base, | 
				
			|||
      port, | 
				
			|||
      { key: sslkey, cert: sslcert } | 
				
			|||
    ); | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  static get wetty() { | 
				
			|||
    return wetty; | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  static init() { | 
				
			|||
    if (!opts.help) { | 
				
			|||
      this.start(opts).catch(err => { | 
				
			|||
        logger.error(err); | 
				
			|||
        process.exitCode = 1; | 
				
			|||
      }); | 
				
			|||
    } else { | 
				
			|||
      optimist.showHelp(); | 
				
			|||
      process.exitCode = 0; | 
				
			|||
    } | 
				
			|||
  } | 
				
			|||
} | 
				
			|||
@ -1,46 +0,0 @@ | 
				
			|||
import compression from 'compression'; | 
				
			|||
import express from 'express'; | 
				
			|||
import favicon from 'serve-favicon'; | 
				
			|||
import helmet from 'helmet'; | 
				
			|||
import http from 'http'; | 
				
			|||
import https from 'https'; | 
				
			|||
import path from 'path'; | 
				
			|||
import socket from 'socket.io'; | 
				
			|||
import { isUndefined } from 'lodash'; | 
				
			|||
import morgan from 'morgan'; | 
				
			|||
import logger from './logger.mjs'; | 
				
			|||
import events from './emitter.mjs'; | 
				
			|||
 | 
				
			|||
const pubDir = path.join(__dirname, '..', 'public'); | 
				
			|||
const distDir = path.join(__dirname, '..', 'dist'); | 
				
			|||
 | 
				
			|||
export default function createServer(base, port, { key, cert }) { | 
				
			|||
  base = base.replace(/\/*$/, ""); | 
				
			|||
  events.emit('debug', `key: ${key}, cert: ${cert}, port: ${port}, base: ${base}`); | 
				
			|||
  const app = express(); | 
				
			|||
  const html = (req, res) => res.sendFile(path.join(pubDir, 'index.html')); | 
				
			|||
  const css = (req, res) => res.sendFile(path.join(distDir, 'main.css')); | 
				
			|||
  const js = (req, res) => res.sendFile(path.join(distDir, 'main.js')); | 
				
			|||
  app | 
				
			|||
    .use(morgan('combined', { stream: logger.stream })) | 
				
			|||
    .use(helmet()) | 
				
			|||
    .use(compression()) | 
				
			|||
    .use(favicon(path.join(pubDir, 'favicon.ico'))) | 
				
			|||
    .get(`${base}/`, html) | 
				
			|||
    .get(`${base}/main.css`, css) | 
				
			|||
    .get(`${base}/main.js`, js) | 
				
			|||
    .get(`${base}/ssh/main.css`, css) | 
				
			|||
    .get(`${base}/ssh/main.js`, js) | 
				
			|||
    .get(`${base}/ssh/:user`, html) | 
				
			|||
 | 
				
			|||
  return socket( | 
				
			|||
    !isUndefined(key) && !isUndefined(cert) | 
				
			|||
      ? https.createServer({ key, cert }, app).listen(port, () => { | 
				
			|||
          events.server(port, 'https'); | 
				
			|||
        }) | 
				
			|||
      : http.createServer(app).listen(port, () => { | 
				
			|||
          events.server(port, 'http'); | 
				
			|||
        }), | 
				
			|||
    { path: `${base}/socket.io` } | 
				
			|||
  ); | 
				
			|||
} | 
				
			|||
@ -1,11 +0,0 @@ | 
				
			|||
import fs from 'fs-extra'; | 
				
			|||
import path from 'path'; | 
				
			|||
import { isUndefined } from 'lodash'; | 
				
			|||
 | 
				
			|||
export default (sslkey, sslcert) => | 
				
			|||
  isUndefined(sslkey) || isUndefined(sslcert) | 
				
			|||
    ? Promise.resolve({}) | 
				
			|||
    : Promise.all([ | 
				
			|||
        fs.readFile(path.resolve(sslkey)), | 
				
			|||
        fs.readFile(path.resolve(sslcert)), | 
				
			|||
      ]).then(([key, cert]) => ({ key, cert })); | 
				
			|||
@ -1,20 +0,0 @@ | 
				
			|||
<!doctype html> | 
				
			|||
<html lang="en"> | 
				
			|||
  <head> | 
				
			|||
    <meta charset="UTF-8"> | 
				
			|||
    <meta http-equiv="X-UA-Compatible" content="IE=edge"> | 
				
			|||
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> | 
				
			|||
    <title>WeTTy - The Web Terminal Emulator</title> | 
				
			|||
    <link rel="stylesheet" href="main.css" /> | 
				
			|||
  </head> | 
				
			|||
  <body> | 
				
			|||
    <div id="overlay"> | 
				
			|||
      <div class="error"> | 
				
			|||
        <div id="msg"></div> | 
				
			|||
        <input type="button" onclick="location.reload();" value="reconnect" /> | 
				
			|||
      </div> | 
				
			|||
    </div> | 
				
			|||
    <div id="terminal"></div> | 
				
			|||
    <script src="main.js"></script> | 
				
			|||
  </body> | 
				
			|||
</html> | 
				
			|||
| 
		 Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 165 KiB  | 
@ -1,39 +0,0 @@ | 
				
			|||
import { isUndefined } from 'lodash'; | 
				
			|||
 | 
				
			|||
export function proposeGeometry({ element, renderer }) { | 
				
			|||
  if (!element.parentElement) return null; | 
				
			|||
 | 
				
			|||
  const parentElementStyle = window.getComputedStyle(element.parentElement); | 
				
			|||
 | 
				
			|||
  return { | 
				
			|||
    cols: Math.floor( | 
				
			|||
      Math.max(0, parseInt(parentElementStyle.getPropertyValue('width'), 10)) / | 
				
			|||
        renderer.dimensions.actualCellWidth, | 
				
			|||
    ), | 
				
			|||
    rows: Math.floor( | 
				
			|||
      parseInt(parentElementStyle.getPropertyValue('height'), 10) / | 
				
			|||
        renderer.dimensions.actualCellHeight, | 
				
			|||
    ), | 
				
			|||
  }; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
export function fit(term) { | 
				
			|||
  const { rows, cols } = proposeGeometry(term); | 
				
			|||
  if (!isUndefined(rows) && !isUndefined(cols)) { | 
				
			|||
    // Force a full render
 | 
				
			|||
    if (term.rows !== rows || term.cols !== cols) { | 
				
			|||
      term.renderer.clear(); | 
				
			|||
      term.resize(cols, rows); | 
				
			|||
    } | 
				
			|||
  } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
export function apply({ prototype }) { | 
				
			|||
  prototype.proposeGeometry = function proProposeGeometry() { | 
				
			|||
    return proposeGeometry(this); | 
				
			|||
  }; | 
				
			|||
 | 
				
			|||
  prototype.fit = function proFit() { | 
				
			|||
    return fit(this); | 
				
			|||
  }; | 
				
			|||
} | 
				
			|||
@ -1,9 +1,9 @@ | 
				
			|||
import rl from 'readline'; | 
				
			|||
import { createInterface } from 'readline'; | 
				
			|||
 | 
				
			|||
ask('Enter your username'); | 
				
			|||
 | 
				
			|||
export default function ask(question) { | 
				
			|||
  const r = rl.createInterface({ | 
				
			|||
export default function ask(question: string): Promise<string> { | 
				
			|||
  const r = createInterface({ | 
				
			|||
    input: process.stdin, | 
				
			|||
    output: process.stdout, | 
				
			|||
  }); | 
				
			|||
@ -0,0 +1,33 @@ | 
				
			|||
import { Socket } from 'socket.io'; | 
				
			|||
import { SSH } from './interfaces'; | 
				
			|||
 | 
				
			|||
const localhost = (host: string): boolean => | 
				
			|||
  process.getuid() === 0 && | 
				
			|||
  (host === 'localhost' || host === '0.0.0.0' || host === '127.0.0.1'); | 
				
			|||
 | 
				
			|||
export default ( | 
				
			|||
  { request: { headers }, client: { conn } }: Socket, | 
				
			|||
  { user, host, port, auth }: SSH | 
				
			|||
): { args: string[]; user: boolean } => ({ | 
				
			|||
  args: localhost(host) | 
				
			|||
    ? ['login', '-h', conn.remoteAddress.split(':')[3]] | 
				
			|||
    : [ | 
				
			|||
        'ssh', | 
				
			|||
        address(headers.referer, user, host), | 
				
			|||
        '-p', | 
				
			|||
        `${port}`, | 
				
			|||
        '-o', | 
				
			|||
        `PreferredAuthentications=${auth}`, | 
				
			|||
      ], | 
				
			|||
  user: | 
				
			|||
    localhost(host) || | 
				
			|||
    user !== '' || | 
				
			|||
    user.includes('@') || | 
				
			|||
    address(headers.referer, user, host).includes('@'), | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
function address(referer: string, user: string, host: string): string { | 
				
			|||
  const match = referer.match('.+/ssh/([^/]+)$'); | 
				
			|||
  const fallback = user ? `${user}@${host}` : host; | 
				
			|||
  return match ? `${match[1]}@${host}` : fallback; | 
				
			|||
} | 
				
			|||
@ -0,0 +1,3 @@ | 
				
			|||
import WeTTy from './wetty'; | 
				
			|||
 | 
				
			|||
export default new WeTTy(); | 
				
			|||
@ -0,0 +1,71 @@ | 
				
			|||
import * as yargs from 'yargs'; | 
				
			|||
import logger from './logger'; | 
				
			|||
import wetty from './emitter'; | 
				
			|||
import WeTTy from './wetty'; | 
				
			|||
 | 
				
			|||
export interface Options { | 
				
			|||
  sshhost: string; | 
				
			|||
  sshport: number; | 
				
			|||
  sshuser: string; | 
				
			|||
  sshauth: string; | 
				
			|||
  sslkey?: string; | 
				
			|||
  sslcert?: string; | 
				
			|||
  base: string; | 
				
			|||
  port: number; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
interface CLI extends Options { | 
				
			|||
  help: boolean; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
export default class Server { | 
				
			|||
  public static start({ | 
				
			|||
    sshuser, | 
				
			|||
    sshhost, | 
				
			|||
    sshauth, | 
				
			|||
    sshport, | 
				
			|||
    base, | 
				
			|||
    port, | 
				
			|||
    sslkey, | 
				
			|||
    sslcert, | 
				
			|||
  }: Options): Promise<void> { | 
				
			|||
    wetty | 
				
			|||
      .on('exit', ({ code, msg }: { code: number; msg: string }) => { | 
				
			|||
        logger.info(`Exit with code: ${code} ${msg}`); | 
				
			|||
      }) | 
				
			|||
      .on('disconnect', () => { | 
				
			|||
        logger.info('disconnect'); | 
				
			|||
      }) | 
				
			|||
      .on('spawn', ({ msg }) => logger.info(msg)) | 
				
			|||
      .on('connection', ({ msg, date }) => logger.info(`${date} ${msg}`)) | 
				
			|||
      .on('server', ({ msg }) => logger.info(msg)) | 
				
			|||
      .on('debug', (msg: string) => logger.debug(msg)); | 
				
			|||
    return wetty.start( | 
				
			|||
      { | 
				
			|||
        user: sshuser, | 
				
			|||
        host: sshhost, | 
				
			|||
        auth: sshauth, | 
				
			|||
        port: sshport, | 
				
			|||
      }, | 
				
			|||
      base, | 
				
			|||
      port, | 
				
			|||
      { key: sslkey, cert: sslcert } | 
				
			|||
    ); | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  public static get wetty(): WeTTy { | 
				
			|||
    return wetty; | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  public static init(opts: CLI): void { | 
				
			|||
    if (!opts.help) { | 
				
			|||
      this.start(opts).catch(err => { | 
				
			|||
        logger.error(err); | 
				
			|||
        process.exitCode = 1; | 
				
			|||
      }); | 
				
			|||
    } else { | 
				
			|||
      yargs.showHelp(); | 
				
			|||
      process.exitCode = 0; | 
				
			|||
    } | 
				
			|||
  } | 
				
			|||
} | 
				
			|||
@ -0,0 +1,16 @@ | 
				
			|||
export interface SSH { | 
				
			|||
  user: string; | 
				
			|||
  host: string; | 
				
			|||
  auth: string; | 
				
			|||
  port: number; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
export interface SSL { | 
				
			|||
  key?: string; | 
				
			|||
  cert?: string; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
export interface SSLBuffer { | 
				
			|||
  key?: Buffer; | 
				
			|||
  cert?: Buffer; | 
				
			|||
} | 
				
			|||
@ -0,0 +1,84 @@ | 
				
			|||
import * as compression from 'compression'; | 
				
			|||
import * as express from 'express'; | 
				
			|||
import * as favicon from 'serve-favicon'; | 
				
			|||
import * as helmet from 'helmet'; | 
				
			|||
import * as http from 'http'; | 
				
			|||
import * as https from 'https'; | 
				
			|||
import * as path from 'path'; | 
				
			|||
import * as socket from 'socket.io'; | 
				
			|||
import { isUndefined } from 'lodash'; | 
				
			|||
import * as morgan from 'morgan'; | 
				
			|||
import logger from './logger'; | 
				
			|||
import events from './emitter'; | 
				
			|||
import { SSLBuffer } from './interfaces'; | 
				
			|||
 | 
				
			|||
const distDir = path.join('./', 'dist', 'client'); | 
				
			|||
 | 
				
			|||
const trim = (str: string): string => str.replace(/\/*$/, ''); | 
				
			|||
 | 
				
			|||
export default function createServer( | 
				
			|||
  base: string, | 
				
			|||
  port: number, | 
				
			|||
  { key, cert }: SSLBuffer | 
				
			|||
): SocketIO.Server { | 
				
			|||
  const basePath = trim(base); | 
				
			|||
  events.emit( | 
				
			|||
    'debug', | 
				
			|||
    `key: ${key}, cert: ${cert}, port: ${port}, base: ${base}` | 
				
			|||
  ); | 
				
			|||
 | 
				
			|||
  const html = ( | 
				
			|||
    req: express.Request, | 
				
			|||
    res: express.Response | 
				
			|||
  ): express.Response => | 
				
			|||
    res.send(`<!doctype html>
 | 
				
			|||
<html lang="en"> | 
				
			|||
  <head> | 
				
			|||
    <meta charset="UTF-8"> | 
				
			|||
    <meta http-equiv="X-UA-Compatible" content="IE=edge"> | 
				
			|||
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> | 
				
			|||
    <title>WeTTy - The Web Terminal Emulator</title> | 
				
			|||
    <link rel="stylesheet" href="${basePath}/public/index.css" /> | 
				
			|||
  </head> | 
				
			|||
  <body> | 
				
			|||
    <div id="overlay"> | 
				
			|||
      <div class="error"> | 
				
			|||
        <div id="msg"></div> | 
				
			|||
        <input type="button" onclick="location.reload();" value="reconnect" /> | 
				
			|||
      </div> | 
				
			|||
    </div> | 
				
			|||
    <div id="terminal"></div> | 
				
			|||
    <script src="${basePath}/public/index.js"></script> | 
				
			|||
  </body> | 
				
			|||
</html>`);
 | 
				
			|||
 | 
				
			|||
  const app = express(); | 
				
			|||
  app | 
				
			|||
    .use(morgan('combined', { stream: logger.stream })) | 
				
			|||
    .use(helmet()) | 
				
			|||
    .use(compression()) | 
				
			|||
    .use(favicon(path.join(distDir, 'favicon.ico'))) | 
				
			|||
    .use(`${basePath}/public`, express.static(distDir)) | 
				
			|||
    .use((req, res, next) => { | 
				
			|||
      if ( | 
				
			|||
        req.url.substr(-1) === '/' && | 
				
			|||
        req.url.length > 1 && | 
				
			|||
        !/\?[^]*\//.test(req.url) | 
				
			|||
      ) | 
				
			|||
        res.redirect(301, req.url.slice(0, -1)); | 
				
			|||
      else next(); | 
				
			|||
    }) | 
				
			|||
    .get(basePath, html) | 
				
			|||
    .get(`${basePath}/ssh/:user`, html); | 
				
			|||
 | 
				
			|||
  return socket( | 
				
			|||
    !isUndefined(key) && !isUndefined(cert) | 
				
			|||
      ? https.createServer({ key, cert }, app).listen(port, () => { | 
				
			|||
          events.server(port, 'https'); | 
				
			|||
        }) | 
				
			|||
      : http.createServer(app).listen(port, () => { | 
				
			|||
          events.server(port, 'http'); | 
				
			|||
        }), | 
				
			|||
    { path: `${basePath}/socket.io` } | 
				
			|||
  ); | 
				
			|||
} | 
				
			|||
@ -0,0 +1,11 @@ | 
				
			|||
import { readFile } from 'fs-extra'; | 
				
			|||
import { resolve } from 'path'; | 
				
			|||
import { isUndefined } from 'lodash'; | 
				
			|||
import { SSL, SSLBuffer } from './interfaces'; | 
				
			|||
 | 
				
			|||
export default async function loadSSL(ssl: SSL): Promise<SSLBuffer> { | 
				
			|||
  if (isUndefined(ssl.key) || isUndefined(ssl.cert)) return {}; | 
				
			|||
  const files = [readFile(resolve(ssl.key)), readFile(resolve(ssl.cert))]; | 
				
			|||
  const [key, cert]: Buffer[] = await Promise.all(files); | 
				
			|||
  return { key, cert }; | 
				
			|||
} | 
				
			|||
@ -0,0 +1,22 @@ | 
				
			|||
{ | 
				
			|||
  "compilerOptions": { | 
				
			|||
    "module": "commonjs", | 
				
			|||
    "outDir": "./dist", | 
				
			|||
    "allowJs": true, | 
				
			|||
    "esModuleInterop": false, | 
				
			|||
    "target": "es6", | 
				
			|||
    "noImplicitAny": true, | 
				
			|||
    "moduleResolution": "node", | 
				
			|||
    "sourceMap": true, | 
				
			|||
    "baseUrl": ".", | 
				
			|||
    "paths": { | 
				
			|||
      "*": [ | 
				
			|||
        "node_modules/", | 
				
			|||
        "src/types/*" | 
				
			|||
      ] | 
				
			|||
    } | 
				
			|||
  }, | 
				
			|||
  "include": [ | 
				
			|||
    "./src/**/*.ts" | 
				
			|||
  ] | 
				
			|||
} | 
				
			|||
@ -0,0 +1,134 @@ | 
				
			|||
import path from 'path'; | 
				
			|||
import webpack from 'webpack'; | 
				
			|||
import MiniCssExtractPlugin from 'mini-css-extract-plugin'; | 
				
			|||
import nodeExternals from 'webpack-node-externals'; | 
				
			|||
 | 
				
			|||
const template = override => | 
				
			|||
  Object.assign( | 
				
			|||
    { | 
				
			|||
      mode: process.env.NODE_ENV || 'development', | 
				
			|||
      resolve: { | 
				
			|||
        modules: [path.resolve(__dirname, 'src'), 'node_modules'], | 
				
			|||
        extensions: ['.ts', '.json', '.js', '.node'], | 
				
			|||
      }, | 
				
			|||
 | 
				
			|||
      stats: { | 
				
			|||
        colors: true, | 
				
			|||
      }, | 
				
			|||
    }, | 
				
			|||
    override | 
				
			|||
  ); | 
				
			|||
 | 
				
			|||
const entry = (folder, file) => | 
				
			|||
  path.join(__dirname, 'src', folder, `${file}.ts`); | 
				
			|||
 | 
				
			|||
const entries = (folder, files) => | 
				
			|||
  Object.assign(...files.map(file => ({ [file]: entry(folder, file) }))); | 
				
			|||
 | 
				
			|||
export default [ | 
				
			|||
  template({ | 
				
			|||
    entry: entries('server', ['index', 'buffer']), | 
				
			|||
    target: 'node', | 
				
			|||
    devtool: 'source-map', | 
				
			|||
    output: { | 
				
			|||
      path: path.resolve(__dirname, 'dist'), | 
				
			|||
      libraryTarget: 'commonjs2', | 
				
			|||
      filename: '[name].js', | 
				
			|||
    }, | 
				
			|||
    externals: [nodeExternals()], | 
				
			|||
    module: { | 
				
			|||
      rules: [ | 
				
			|||
        { | 
				
			|||
          test: /\.ts$/, | 
				
			|||
          use: { | 
				
			|||
            loader: 'babel-loader', | 
				
			|||
            options: { | 
				
			|||
              presets: [ | 
				
			|||
                '@babel/preset-typescript', | 
				
			|||
                [ | 
				
			|||
                  '@babel/preset-env', | 
				
			|||
                  { | 
				
			|||
                    targets: { | 
				
			|||
                      node: 'current', | 
				
			|||
                    }, | 
				
			|||
                  }, | 
				
			|||
                ], | 
				
			|||
              ], | 
				
			|||
              plugins: ['lodash'], | 
				
			|||
            }, | 
				
			|||
          }, | 
				
			|||
        }, | 
				
			|||
        { | 
				
			|||
          test: /\.js$/, | 
				
			|||
          use: ['source-map-loader'], | 
				
			|||
          enforce: 'pre', | 
				
			|||
        }, | 
				
			|||
      ], | 
				
			|||
    }, | 
				
			|||
    plugins: [new webpack.IgnorePlugin(/uws/)], | 
				
			|||
  }), | 
				
			|||
  template({ | 
				
			|||
    entry: entries('client', ['index']), | 
				
			|||
    output: { | 
				
			|||
      path: path.resolve(__dirname, 'dist', 'client'), | 
				
			|||
      filename: '[name].js', | 
				
			|||
    }, | 
				
			|||
    module: { | 
				
			|||
      rules: [ | 
				
			|||
        { | 
				
			|||
          test: /\.ts$/, | 
				
			|||
          use: { | 
				
			|||
            loader: 'babel-loader', | 
				
			|||
            options: { | 
				
			|||
              presets: [ | 
				
			|||
                '@babel/preset-typescript', | 
				
			|||
                [ | 
				
			|||
                  '@babel/preset-env', | 
				
			|||
                  { | 
				
			|||
                    targets: { | 
				
			|||
                      browsers: ['last 2 versions', 'safari >= 7'], | 
				
			|||
                    }, | 
				
			|||
                  }, | 
				
			|||
                ], | 
				
			|||
              ], | 
				
			|||
              plugins: ['lodash'], | 
				
			|||
            }, | 
				
			|||
          }, | 
				
			|||
        }, | 
				
			|||
        { | 
				
			|||
          test: /\.js$/, | 
				
			|||
          use: ['source-map-loader'], | 
				
			|||
          enforce: 'pre', | 
				
			|||
        }, | 
				
			|||
        { | 
				
			|||
          test: /\.scss$/, | 
				
			|||
          use: [ | 
				
			|||
            { | 
				
			|||
              loader: MiniCssExtractPlugin.loader, | 
				
			|||
            }, | 
				
			|||
            { | 
				
			|||
              loader: 'css-loader', | 
				
			|||
            }, | 
				
			|||
            { | 
				
			|||
              loader: 'sass-loader', | 
				
			|||
            }, | 
				
			|||
          ], | 
				
			|||
        }, | 
				
			|||
        { | 
				
			|||
          test: /\.(jpg|jpeg|png|gif|mp3|svg|ico)$/, | 
				
			|||
          loader: 'file-loader', | 
				
			|||
          options: { | 
				
			|||
            name: '[name].[ext]', | 
				
			|||
          }, | 
				
			|||
        }, | 
				
			|||
      ], | 
				
			|||
    }, | 
				
			|||
    plugins: [ | 
				
			|||
      new MiniCssExtractPlugin({ | 
				
			|||
        filename: '[name].css', | 
				
			|||
        chunkFilename: '[id].css', | 
				
			|||
      }), | 
				
			|||
    ], | 
				
			|||
    devtool: 'source-map', | 
				
			|||
  }), | 
				
			|||
]; | 
				
			|||
@ -1,74 +0,0 @@ | 
				
			|||
const webpack = require('webpack'); | 
				
			|||
const path = require('path'); | 
				
			|||
const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); | 
				
			|||
const ExtractTextPlugin = require('extract-text-webpack-plugin'); | 
				
			|||
 | 
				
			|||
const extractSass = new ExtractTextPlugin({ | 
				
			|||
  filename: '[name].css', | 
				
			|||
  disable: process.env.NODE_ENV === 'development', | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
const loader = new webpack.ProvidePlugin({ | 
				
			|||
  fetch: 'imports-loader?this=>global!exports-loader?global.fetch!whatwg-fetch', | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
module.exports = { | 
				
			|||
  entry: './src/index.js', | 
				
			|||
  output: { | 
				
			|||
    filename: '[name].js', | 
				
			|||
    path: path.resolve(__dirname, 'dist'), | 
				
			|||
  }, | 
				
			|||
  module: { | 
				
			|||
    loaders: [ | 
				
			|||
      { | 
				
			|||
        test: /\.js$/, | 
				
			|||
        loader: 'babel-loader', | 
				
			|||
        exclude: /node_modules/, | 
				
			|||
        query: { | 
				
			|||
          plugins: ['lodash'], | 
				
			|||
          presets: [ | 
				
			|||
            [ | 
				
			|||
              'env', | 
				
			|||
              { | 
				
			|||
                targets: { | 
				
			|||
                  browsers: ['last 2 versions', 'safari >= 7'], | 
				
			|||
                }, | 
				
			|||
              }, | 
				
			|||
            ], | 
				
			|||
          ], | 
				
			|||
        }, | 
				
			|||
      }, | 
				
			|||
      { | 
				
			|||
        test: /\.scss$/, | 
				
			|||
        use: extractSass.extract({ | 
				
			|||
          use: [ | 
				
			|||
            { | 
				
			|||
              loader: 'css-loader', | 
				
			|||
              options: { minimize: true }, | 
				
			|||
            }, | 
				
			|||
            { | 
				
			|||
              loader: 'sass-loader', | 
				
			|||
            }, | 
				
			|||
          ], | 
				
			|||
          fallback: 'style-loader', | 
				
			|||
        }), | 
				
			|||
      }, | 
				
			|||
    ], | 
				
			|||
  }, | 
				
			|||
  plugins: | 
				
			|||
    process.env.NODE_ENV !== 'development' | 
				
			|||
      ? [ | 
				
			|||
          loader, | 
				
			|||
          extractSass, | 
				
			|||
          new UglifyJSPlugin({ | 
				
			|||
            parallel: true, | 
				
			|||
            uglifyOptions: { | 
				
			|||
              ecma: 8, | 
				
			|||
            }, | 
				
			|||
          }), | 
				
			|||
        ] | 
				
			|||
      : [loader, extractSass], | 
				
			|||
  stats: { | 
				
			|||
    colors: true, | 
				
			|||
  }, | 
				
			|||
}; | 
				
			|||
								
									
										File diff suppressed because it is too large
									
								
							
						
					
					Loading…
					
					
				
		Reference in new issue