Initial commit

This commit is contained in:
matt
2020-09-20 12:16:44 +00:00
parent 09a4460ddb
commit 408c005d3e
839 changed files with 190481 additions and 0 deletions

8
kasmweb/utils/.eslintrc Normal file
View File

@@ -0,0 +1,8 @@
{
"env": {
"node": true
},
"rules": {
"no-console": 0
}
}

14
kasmweb/utils/README.md Normal file
View File

@@ -0,0 +1,14 @@
## WebSockets Proxy/Bridge
Websockify has been forked out into its own project. `launch.sh` wil
automatically download it here if it is not already present and not
installed as system-wide.
For more detailed description and usage information please refer to
the [websockify README](https://github.com/novnc/websockify/blob/master/README.md).
The other versions of websockify (C, Node.js) and the associated test
programs have been moved to
[websockify](https://github.com/novnc/websockify). Websockify was
formerly named wsproxy.

17
kasmweb/utils/b64-to-binary.pl Executable file
View File

@@ -0,0 +1,17 @@
#!/usr/bin/env perl
use MIME::Base64;
for (<>) {
unless (/^'([{}])(\d+)\1(.+?)',$/) {
print;
next;
}
my ($dir, $amt, $b64) = ($1, $2, $3);
my $decoded = MIME::Base64::decode($b64) or die "Could not base64-decode line `$_`";
my $decoded_escaped = join "", map { "\\x$_" } unpack("(H2)*", $decoded);
print "'${dir}${amt}${dir}${decoded_escaped}',\n";
}

127
kasmweb/utils/genkeysymdef.js Executable file
View File

@@ -0,0 +1,127 @@
#!/usr/bin/env node
/*
* genkeysymdef: X11 keysymdef.h to JavaScript converter
* Copyright (C) 2018 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*/
"use strict";
const fs = require('fs');
let show_help = process.argv.length === 2;
let filename;
for (let i = 2; i < process.argv.length; ++i) {
switch (process.argv[i]) {
case "--help":
case "-h":
show_help = true;
break;
case "--file":
case "-f":
default:
filename = process.argv[i];
}
}
if (!filename) {
show_help = true;
console.log("Error: No filename specified\n");
}
if (show_help) {
console.log("Parses a *nix keysymdef.h to generate Unicode code point mappings");
console.log("Usage: node parse.js [options] filename:");
console.log(" -h [ --help ] Produce this help message");
console.log(" filename The keysymdef.h file to parse");
process.exit(0);
}
const buf = fs.readFileSync(filename);
const str = buf.toString('utf8');
const re = /^#define XK_([a-zA-Z_0-9]+)\s+0x([0-9a-fA-F]+)\s*(\/\*\s*(.*)\s*\*\/)?\s*$/m;
const arr = str.split('\n');
const codepoints = {};
for (let i = 0; i < arr.length; ++i) {
const result = re.exec(arr[i]);
if (result) {
const keyname = result[1];
const keysym = parseInt(result[2], 16);
const remainder = result[3];
const unicodeRes = /U\+([0-9a-fA-F]+)/.exec(remainder);
if (unicodeRes) {
const unicode = parseInt(unicodeRes[1], 16);
// The first entry is the preferred one
if (!codepoints[unicode]) {
codepoints[unicode] = { keysym: keysym, name: keyname };
}
}
}
}
let out =
"/*\n" +
" * Mapping from Unicode codepoints to X11/RFB keysyms\n" +
" *\n" +
" * This file was automatically generated from keysymdef.h\n" +
" * DO NOT EDIT!\n" +
" */\n" +
"\n" +
"/* Functions at the bottom */\n" +
"\n" +
"const codepoints = {\n";
function toHex(num) {
let s = num.toString(16);
if (s.length < 4) {
s = ("0000" + s).slice(-4);
}
return "0x" + s;
}
for (let codepoint in codepoints) {
codepoint = parseInt(codepoint);
// Latin-1?
if ((codepoint >= 0x20) && (codepoint <= 0xff)) {
continue;
}
// Handled by the general Unicode mapping?
if ((codepoint | 0x01000000) === codepoints[codepoint].keysym) {
continue;
}
out += " " + toHex(codepoint) + ": " +
toHex(codepoints[codepoint].keysym) +
", // XK_" + codepoints[codepoint].name + "\n";
}
out +=
"};\n" +
"\n" +
"export default {\n" +
" lookup(u) {\n" +
" // Latin-1 is one-to-one mapping\n" +
" if ((u >= 0x20) && (u <= 0xff)) {\n" +
" return u;\n" +
" }\n" +
"\n" +
" // Lookup table (fairly random)\n" +
" const keysym = codepoints[u];\n" +
" if (keysym !== undefined) {\n" +
" return keysym;\n" +
" }\n" +
"\n" +
" // General mapping as final fallback\n" +
" return 0x01000000 | u;\n" +
" },\n" +
"};";
console.log(out);

40
kasmweb/utils/img2js.py Executable file
View File

@@ -0,0 +1,40 @@
#!/usr/bin/env python
#
# Convert image to Javascript compatible base64 Data URI
# Copyright (C) 2018 The noVNC Authors
# Licensed under MPL 2.0 (see docs/LICENSE.MPL-2.0)
#
import sys, base64
try:
from PIL import Image
except:
print "python PIL module required (python-imaging package)"
sys.exit(1)
if len(sys.argv) < 3:
print "Usage: %s IMAGE JS_VARIABLE" % sys.argv[0]
sys.exit(1)
fname = sys.argv[1]
var = sys.argv[2]
ext = fname.lower().split('.')[-1]
if ext == "png": mime = "image/png"
elif ext in ["jpg", "jpeg"]: mime = "image/jpeg"
elif ext == "gif": mime = "image/gif"
else:
print "Only PNG, JPEG and GIF images are supported"
sys.exit(1)
uri = "data:%s;base64," % mime
im = Image.open(fname)
w, h = im.size
raw = open(fname).read()
print '%s = {"width": %s, "height": %s, "data": "%s%s"};' % (
var, w, h, uri, base64.b64encode(raw))

206
kasmweb/utils/json2graph.py Executable file
View File

@@ -0,0 +1,206 @@
#!/usr/bin/env python
'''
Use matplotlib to generate performance charts
Copyright (C) 2018 The noVNC Authors
Licensed under MPL-2.0 (see docs/LICENSE.MPL-2.0)
'''
# a bar plot with errorbars
import sys, json
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
def usage():
print "%s json_file level1 level2 level3 [legend_height]\n\n" % sys.argv[0]
print "Description:\n"
print "level1, level2, and level3 are one each of the following:\n";
print " select=ITEM - select only ITEM at this level";
print " bar - each item on this level becomes a graph bar";
print " group - items on this level become groups of bars";
print "\n";
print "json_file is a file containing json data in the following format:\n"
print ' {';
print ' "conf": {';
print ' "order_l1": [';
print ' "level1_label1",';
print ' "level1_label2",';
print ' ...';
print ' ],';
print ' "order_l2": [';
print ' "level2_label1",';
print ' "level2_label2",';
print ' ...';
print ' ],';
print ' "order_l3": [';
print ' "level3_label1",';
print ' "level3_label2",';
print ' ...';
print ' ]';
print ' },';
print ' "stats": {';
print ' "level1_label1": {';
print ' "level2_label1": {';
print ' "level3_label1": [val1, val2, val3],';
print ' "level3_label2": [val1, val2, val3],';
print ' ...';
print ' },';
print ' "level2_label2": {';
print ' ...';
print ' },';
print ' },';
print ' "level1_label2": {';
print ' ...';
print ' },';
print ' ...';
print ' },';
print ' }';
sys.exit(2)
def error(msg):
print msg
sys.exit(1)
#colors = ['#ff0000', '#0863e9', '#00f200', '#ffa100',
# '#800000', '#805100', '#013075', '#007900']
colors = ['#ff0000', '#00ff00', '#0000ff',
'#dddd00', '#dd00dd', '#00dddd',
'#dd6622', '#dd2266', '#66dd22',
'#8844dd', '#44dd88', '#4488dd']
if len(sys.argv) < 5:
usage()
filename = sys.argv[1]
L1 = sys.argv[2]
L2 = sys.argv[3]
L3 = sys.argv[4]
if len(sys.argv) > 5:
legendHeight = float(sys.argv[5])
else:
legendHeight = 0.75
# Load the JSON data from the file
data = json.loads(file(filename).read())
conf = data['conf']
stats = data['stats']
# Sanity check data hierarchy
if len(conf['order_l1']) != len(stats.keys()):
error("conf.order_l1 does not match stats level 1")
for l1 in stats.keys():
if len(conf['order_l2']) != len(stats[l1].keys()):
error("conf.order_l2 does not match stats level 2 for %s" % l1)
if conf['order_l1'].count(l1) < 1:
error("%s not found in conf.order_l1" % l1)
for l2 in stats[l1].keys():
if len(conf['order_l3']) != len(stats[l1][l2].keys()):
error("conf.order_l3 does not match stats level 3")
if conf['order_l2'].count(l2) < 1:
error("%s not found in conf.order_l2" % l2)
for l3 in stats[l1][l2].keys():
if conf['order_l3'].count(l3) < 1:
error("%s not found in conf.order_l3" % l3)
#
# Generate the data based on the level specifications
#
bar_labels = None
group_labels = None
bar_vals = []
bar_sdvs = []
if L3.startswith("select="):
select_label = l3 = L3.split("=")[1]
bar_labels = conf['order_l1']
group_labels = conf['order_l2']
bar_vals = [[0]*len(group_labels) for i in bar_labels]
bar_sdvs = [[0]*len(group_labels) for i in bar_labels]
for b in range(len(bar_labels)):
l1 = bar_labels[b]
for g in range(len(group_labels)):
l2 = group_labels[g]
bar_vals[b][g] = np.mean(stats[l1][l2][l3])
bar_sdvs[b][g] = np.std(stats[l1][l2][l3])
elif L2.startswith("select="):
select_label = l2 = L2.split("=")[1]
bar_labels = conf['order_l1']
group_labels = conf['order_l3']
bar_vals = [[0]*len(group_labels) for i in bar_labels]
bar_sdvs = [[0]*len(group_labels) for i in bar_labels]
for b in range(len(bar_labels)):
l1 = bar_labels[b]
for g in range(len(group_labels)):
l3 = group_labels[g]
bar_vals[b][g] = np.mean(stats[l1][l2][l3])
bar_sdvs[b][g] = np.std(stats[l1][l2][l3])
elif L1.startswith("select="):
select_label = l1 = L1.split("=")[1]
bar_labels = conf['order_l2']
group_labels = conf['order_l3']
bar_vals = [[0]*len(group_labels) for i in bar_labels]
bar_sdvs = [[0]*len(group_labels) for i in bar_labels]
for b in range(len(bar_labels)):
l2 = bar_labels[b]
for g in range(len(group_labels)):
l3 = group_labels[g]
bar_vals[b][g] = np.mean(stats[l1][l2][l3])
bar_sdvs[b][g] = np.std(stats[l1][l2][l3])
else:
usage()
# If group is before bar then flip (zip) the data
if [L1, L2, L3].index("group") < [L1, L2, L3].index("bar"):
bar_labels, group_labels = group_labels, bar_labels
bar_vals = zip(*bar_vals)
bar_sdvs = zip(*bar_sdvs)
print "bar_vals:", bar_vals
#
# Now render the bar graph
#
ind = np.arange(len(group_labels)) # the x locations for the groups
width = 0.8 * (1.0/len(bar_labels)) # the width of the bars
fig = plt.figure(figsize=(10,6), dpi=80)
plot = fig.add_subplot(1, 1, 1)
rects = []
for i in range(len(bar_vals)):
rects.append(plot.bar(ind+width*i, bar_vals[i], width, color=colors[i],
yerr=bar_sdvs[i], align='center'))
# add some
plot.set_ylabel('Milliseconds (less is better)')
plot.set_title("Javascript array test: %s" % select_label)
plot.set_xticks(ind+width)
plot.set_xticklabels( group_labels )
fontP = FontProperties()
fontP.set_size('small')
plot.legend( [r[0] for r in rects], bar_labels, prop=fontP,
loc = 'center right', bbox_to_anchor = (1.0, legendHeight))
def autolabel(rects):
# attach some text labels
for rect in rects:
height = rect.get_height()
if np.isnan(height):
height = 0.0
plot.text(rect.get_x()+rect.get_width()/2., height+20, '%d'%int(height),
ha='center', va='bottom', size='7')
for rect in rects:
autolabel(rect)
# Adjust axis sizes
axis = list(plot.axis())
axis[0] = -width # Make sure left side has enough for bar
#axis[1] = axis[1] * 1.20 # Add 20% to the right to make sure it fits
axis[2] = 0 # Make y-axis start at 0
axis[3] = axis[3] * 1.10 # Add 10% to the top
plot.axis(axis)
plt.show()

169
kasmweb/utils/launch.sh Executable file
View File

@@ -0,0 +1,169 @@
#!/usr/bin/env bash
# Copyright (C) 2018 The noVNC Authors
# Licensed under MPL 2.0 or any later version (see LICENSE.txt)
usage() {
if [ "$*" ]; then
echo "$*"
echo
fi
echo "Usage: ${NAME} [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]"
echo
echo "Starts the WebSockets proxy and a mini-webserver and "
echo "provides a cut-and-paste URL to go to."
echo
echo " --listen PORT Port for proxy/webserver to listen on"
echo " Default: 6080"
echo " --vnc VNC_HOST:PORT VNC server host:port proxy target"
echo " Default: localhost:5900"
echo " --cert CERT Path to combined cert/key file"
echo " Default: self.pem"
echo " --web WEB Path to web files (e.g. vnc.html)"
echo " Default: ./"
echo " --ssl-only Disable non-https connections."
echo " "
echo " --record FILE Record traffic to FILE.session.js"
echo " "
exit 2
}
NAME="$(basename $0)"
REAL_NAME="$(readlink -f $0)"
HERE="$(cd "$(dirname "$REAL_NAME")" && pwd)"
PORT="6080"
VNC_DEST="localhost:5900"
CERT=""
WEB=""
proxy_pid=""
SSLONLY=""
RECORD_ARG=""
die() {
echo "$*"
exit 1
}
cleanup() {
trap - TERM QUIT INT EXIT
trap "true" CHLD # Ignore cleanup messages
echo
if [ -n "${proxy_pid}" ]; then
echo "Terminating WebSockets proxy (${proxy_pid})"
kill ${proxy_pid}
fi
}
# Process Arguments
# Arguments that only apply to chrooter itself
while [ "$*" ]; do
param=$1; shift; OPTARG=$1
case $param in
--listen) PORT="${OPTARG}"; shift ;;
--vnc) VNC_DEST="${OPTARG}"; shift ;;
--cert) CERT="${OPTARG}"; shift ;;
--web) WEB="${OPTARG}"; shift ;;
--ssl-only) SSLONLY="--ssl-only" ;;
--record) RECORD_ARG="--record ${OPTARG}"; shift ;;
-h|--help) usage ;;
-*) usage "Unknown chrooter option: ${param}" ;;
*) break ;;
esac
done
# Sanity checks
if bash -c "exec 7<>/dev/tcp/localhost/${PORT}" &> /dev/null; then
exec 7<&-
exec 7>&-
die "Port ${PORT} in use. Try --listen PORT"
else
exec 7<&-
exec 7>&-
fi
trap "cleanup" TERM QUIT INT EXIT
# Find vnc.html
if [ -n "${WEB}" ]; then
if [ ! -e "${WEB}/vnc.html" ]; then
die "Could not find ${WEB}/vnc.html"
fi
elif [ -e "$(pwd)/vnc.html" ]; then
WEB=$(pwd)
elif [ -e "${HERE}/../vnc.html" ]; then
WEB=${HERE}/../
elif [ -e "${HERE}/vnc.html" ]; then
WEB=${HERE}
elif [ -e "${HERE}/../share/novnc/vnc.html" ]; then
WEB=${HERE}/../share/novnc/
else
die "Could not find vnc.html"
fi
# Find self.pem
if [ -n "${CERT}" ]; then
if [ ! -e "${CERT}" ]; then
die "Could not find ${CERT}"
fi
elif [ -e "$(pwd)/self.pem" ]; then
CERT="$(pwd)/self.pem"
elif [ -e "${HERE}/../self.pem" ]; then
CERT="${HERE}/../self.pem"
elif [ -e "${HERE}/self.pem" ]; then
CERT="${HERE}/self.pem"
else
echo "Warning: could not find self.pem"
fi
# try to find websockify (prefer local, try global, then download local)
if [[ -e ${HERE}/websockify ]]; then
WEBSOCKIFY=${HERE}/websockify/run
if [[ ! -x $WEBSOCKIFY ]]; then
echo "The path ${HERE}/websockify exists, but $WEBSOCKIFY either does not exist or is not executable."
echo "If you intended to use an installed websockify package, please remove ${HERE}/websockify."
exit 1
fi
echo "Using local websockify at $WEBSOCKIFY"
else
WEBSOCKIFY=$(which websockify 2>/dev/null)
if [[ $? -ne 0 ]]; then
echo "No installed websockify, attempting to clone websockify..."
WEBSOCKIFY=${HERE}/websockify/run
git clone https://github.com/novnc/websockify ${HERE}/websockify
if [[ ! -e $WEBSOCKIFY ]]; then
echo "Unable to locate ${HERE}/websockify/run after downloading"
exit 1
fi
echo "Using local websockify at $WEBSOCKIFY"
else
echo "Using installed websockify at $WEBSOCKIFY"
fi
fi
echo "Starting webserver and WebSockets proxy on port ${PORT}"
#${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
${WEBSOCKIFY} ${SSLONLY} --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} ${RECORD_ARG} &
proxy_pid="$!"
sleep 1
if ! ps -p ${proxy_pid} >/dev/null; then
proxy_pid=
echo "Failed to start WebSockets proxy"
exit 1
fi
echo -e "\n\nNavigate to this URL:\n"
if [ "x$SSLONLY" == "x" ]; then
echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
else
echo -e " https://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
fi
echo -e "Press Ctrl-C to exit\n\n"
wait ${proxy_pid}

28
kasmweb/utils/u2x11 Executable file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
#
# Convert "U+..." commented entries in /usr/include/X11/keysymdef.h
# into JavaScript for use by noVNC. Note this is likely to produce
# a few duplicate properties with clashing values, that will need
# resolving manually.
#
# Colin Dean <colin@xvpsource.org>
#
regex="^#define[ \t]+XK_[A-Za-z0-9_]+[ \t]+0x([0-9a-fA-F]+)[ \t]+\/\*[ \t]+U\+([0-9a-fA-F]+)[ \t]+[^*]+.[ \t]+\*\/[ \t]*$"
echo "unicodeTable = {"
while read line; do
if echo "${line}" | egrep -qs "${regex}"; then
x11=$(echo "${line}" | sed -r "s/${regex}/\1/")
vnc=$(echo "${line}" | sed -r "s/${regex}/\2/")
if echo "${vnc}" | egrep -qs "^00[2-9A-F][0-9A-F]$"; then
: # skip ISO Latin-1 (U+0020 to U+00FF) as 1-to-1 mapping
else
# note 1-to-1 is possible (e.g. for Euro symbol, U+20AC)
echo " 0x${vnc} : 0x${x11},"
fi
fi
done < /usr/include/X11/keysymdef.h | uniq
echo "};"

313
kasmweb/utils/use_require.js Executable file
View File

@@ -0,0 +1,313 @@
#!/usr/bin/env node
const path = require('path');
const program = require('commander');
const fs = require('fs');
const fse = require('fs-extra');
const babel = require('babel-core');
const SUPPORTED_FORMATS = new Set(['amd', 'commonjs', 'systemjs', 'umd']);
program
.option('--as [format]', `output files using various import formats instead of ES6 import and export. Supports ${Array.from(SUPPORTED_FORMATS)}.`)
.option('-m, --with-source-maps [type]', 'output source maps when not generating a bundled app (type may be empty for external source maps, inline for inline source maps, or both) ')
.option('--with-app', 'process app files as well as core files')
.option('--only-legacy', 'only output legacy files (no ES6 modules) for the app')
.option('--clean', 'clear the lib folder before building')
.parse(process.argv);
// the various important paths
const paths = {
main: path.resolve(__dirname, '..'),
core: path.resolve(__dirname, '..', 'core'),
app: path.resolve(__dirname, '..', 'app'),
vendor: path.resolve(__dirname, '..', 'vendor'),
out_dir_base: path.resolve(__dirname, '..', 'build'),
lib_dir_base: path.resolve(__dirname, '..', 'lib'),
};
const no_copy_files = new Set([
// skip these -- they don't belong in the processed application
path.join(paths.vendor, 'sinon.js'),
path.join(paths.vendor, 'browser-es-module-loader'),
path.join(paths.vendor, 'promise.js'),
path.join(paths.app, 'images', 'icons', 'Makefile'),
]);
const no_transform_files = new Set([
// don't transform this -- we want it imported as-is to properly catch loading errors
path.join(paths.app, 'error-handler.js'),
]);
no_copy_files.forEach(file => no_transform_files.add(file));
// util.promisify requires Node.js 8.x, so we have our own
function promisify(original) {
return function promise_wrap() {
const args = Array.prototype.slice.call(arguments);
return new Promise((resolve, reject) => {
original.apply(this, args.concat((err, value) => {
if (err) return reject(err);
resolve(value);
}));
});
};
}
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
const readdir = promisify(fs.readdir);
const lstat = promisify(fs.lstat);
const copy = promisify(fse.copy);
const unlink = promisify(fse.unlink);
const ensureDir = promisify(fse.ensureDir);
const rmdir = promisify(fse.rmdir);
const babelTransformFile = promisify(babel.transformFile);
// walkDir *recursively* walks directories trees,
// calling the callback for all normal files found.
function walkDir(base_path, cb, filter) {
return readdir(base_path)
.then((files) => {
const paths = files.map(filename => path.join(base_path, filename));
return Promise.all(paths.map(filepath => lstat(filepath)
.then((stats) => {
if (filter !== undefined && !filter(filepath, stats)) return;
if (stats.isSymbolicLink()) return;
if (stats.isFile()) return cb(filepath);
if (stats.isDirectory()) return walkDir(filepath, cb, filter);
})));
});
}
function transform_html(legacy_scripts, only_legacy) {
// write out the modified vnc.html file that works with the bundle
const src_html_path = path.resolve(__dirname, '..', 'vnc.html');
const out_html_path = path.resolve(paths.out_dir_base, 'vnc.html');
return readFile(src_html_path)
.then((contents_raw) => {
let contents = contents_raw.toString();
const start_marker = '<!-- begin scripts -->\n';
const end_marker = '<!-- end scripts -->';
const start_ind = contents.indexOf(start_marker) + start_marker.length;
const end_ind = contents.indexOf(end_marker, start_ind);
let new_script = '';
if (only_legacy) {
// Only legacy version, so include things directly
for (let i = 0;i < legacy_scripts.length;i++) {
new_script += ` <script src="${legacy_scripts[i]}"></script>\n`;
}
} else {
// Otherwise detect if it's a modern browser and select
// variant accordingly
new_script += `\
<script type="module">\n\
window._noVNC_has_module_support = true;\n\
</script>\n\
<script>\n\
window.addEventListener("load", function() {\n\
if (window._noVNC_has_module_support) return;\n\
let legacy_scripts = ${JSON.stringify(legacy_scripts)};\n\
for (let i = 0;i < legacy_scripts.length;i++) {\n\
let script = document.createElement("script");\n\
script.src = legacy_scripts[i];\n\
script.async = false;\n\
document.head.appendChild(script);\n\
}\n\
});\n\
</script>\n`;
// Original, ES6 modules
new_script += ' <script type="module" crossorigin="anonymous" src="app/ui.js"></script>\n';
}
contents = contents.slice(0, start_ind) + `${new_script}\n` + contents.slice(end_ind);
return contents;
})
.then((contents) => {
console.log(`Writing ${out_html_path}`);
return writeFile(out_html_path, contents);
});
}
function make_lib_files(import_format, source_maps, with_app_dir, only_legacy) {
if (!import_format) {
throw new Error("you must specify an import format to generate compiled noVNC libraries");
} else if (!SUPPORTED_FORMATS.has(import_format)) {
throw new Error(`unsupported output format "${import_format}" for import/export -- only ${Array.from(SUPPORTED_FORMATS)} are supported`);
}
// NB: we need to make a copy of babel_opts, since babel sets some defaults on it
const babel_opts = () => ({
plugins: [`transform-es2015-modules-${import_format}`],
presets: ['es2015'],
ast: false,
sourceMaps: source_maps,
});
// No point in duplicate files without the app, so force only converted files
if (!with_app_dir) {
only_legacy = true;
}
let in_path;
let out_path_base;
if (with_app_dir) {
out_path_base = paths.out_dir_base;
in_path = paths.main;
} else {
out_path_base = paths.lib_dir_base;
}
const legacy_path_base = only_legacy ? out_path_base : path.join(out_path_base, 'legacy');
fse.ensureDirSync(out_path_base);
const helpers = require('./use_require_helpers');
const helper = helpers[import_format];
const outFiles = [];
const handleDir = (js_only, vendor_rewrite, in_path_base, filename) => Promise.resolve()
.then(() => {
if (no_copy_files.has(filename)) return;
const out_path = path.join(out_path_base, path.relative(in_path_base, filename));
const legacy_path = path.join(legacy_path_base, path.relative(in_path_base, filename));
if (path.extname(filename) !== '.js') {
if (!js_only) {
console.log(`Writing ${out_path}`);
return copy(filename, out_path);
}
return; // skip non-javascript files
}
return Promise.resolve()
.then(() => {
if (only_legacy && !no_transform_files.has(filename)) {
return;
}
return ensureDir(path.dirname(out_path))
.then(() => {
console.log(`Writing ${out_path}`);
return copy(filename, out_path);
});
})
.then(() => ensureDir(path.dirname(legacy_path)))
.then(() => {
if (no_transform_files.has(filename)) {
return;
}
const opts = babel_opts();
if (helper && helpers.optionsOverride) {
helper.optionsOverride(opts);
}
// Adjust for the fact that we move the core files relative
// to the vendor directory
if (vendor_rewrite) {
opts.plugins.push(["import-redirect",
{"root": legacy_path_base,
"redirect": { "vendor/(.+)": "./vendor/$1"}}]);
}
return babelTransformFile(filename, opts)
.then((res) => {
console.log(`Writing ${legacy_path}`);
const {map} = res;
let {code} = res;
if (source_maps === true) {
// append URL for external source map
code += `\n//# sourceMappingURL=${path.basename(legacy_path)}.map\n`;
}
outFiles.push(`${legacy_path}`);
return writeFile(legacy_path, code)
.then(() => {
if (source_maps === true || source_maps === 'both') {
console.log(` and ${legacy_path}.map`);
outFiles.push(`${legacy_path}.map`);
return writeFile(`${legacy_path}.map`, JSON.stringify(map));
}
});
});
});
});
if (with_app_dir && helper && helper.noCopyOverride) {
helper.noCopyOverride(paths, no_copy_files);
}
Promise.resolve()
.then(() => {
const handler = handleDir.bind(null, true, false, in_path || paths.main);
const filter = (filename, stats) => !no_copy_files.has(filename);
return walkDir(paths.vendor, handler, filter);
})
.then(() => {
const handler = handleDir.bind(null, true, !in_path, in_path || paths.core);
const filter = (filename, stats) => !no_copy_files.has(filename);
return walkDir(paths.core, handler, filter);
})
.then(() => {
if (!with_app_dir) return;
const handler = handleDir.bind(null, false, false, in_path);
const filter = (filename, stats) => !no_copy_files.has(filename);
return walkDir(paths.app, handler, filter);
})
.then(() => {
if (!with_app_dir) return;
if (!helper || !helper.appWriter) {
throw new Error(`Unable to generate app for the ${import_format} format!`);
}
const out_app_path = path.join(legacy_path_base, 'app.js');
console.log(`Writing ${out_app_path}`);
return helper.appWriter(out_path_base, legacy_path_base, out_app_path)
.then((extra_scripts) => {
const rel_app_path = path.relative(out_path_base, out_app_path);
const legacy_scripts = extra_scripts.concat([rel_app_path]);
transform_html(legacy_scripts, only_legacy);
})
.then(() => {
if (!helper.removeModules) return;
console.log(`Cleaning up temporary files...`);
return Promise.all(outFiles.map((filepath) => {
unlink(filepath)
.then(() => {
// Try to clean up any empty directories if this
// was the last file in there
const rmdir_r = dir =>
rmdir(dir)
.then(() => rmdir_r(path.dirname(dir)))
.catch(() => {
// Assume the error was ENOTEMPTY and ignore it
});
return rmdir_r(path.dirname(filepath));
});
}));
});
})
.catch((err) => {
console.error(`Failure converting modules: ${err}`);
process.exit(1);
});
}
if (program.clean) {
console.log(`Removing ${paths.lib_dir_base}`);
fse.removeSync(paths.lib_dir_base);
console.log(`Removing ${paths.out_dir_base}`);
fse.removeSync(paths.out_dir_base);
}
make_lib_files(program.as, program.withSourceMaps, program.withApp, program.onlyLegacy);

View File

@@ -0,0 +1,76 @@
// writes helpers require for vnc.html (they should output app.js)
const fs = require('fs');
const path = require('path');
// util.promisify requires Node.js 8.x, so we have our own
function promisify(original) {
return function promise_wrap() {
const args = Array.prototype.slice.call(arguments);
return new Promise((resolve, reject) => {
original.apply(this, args.concat((err, value) => {
if (err) return reject(err);
resolve(value);
}));
});
};
}
const writeFile = promisify(fs.writeFile);
module.exports = {
'amd': {
appWriter: (base_out_path, script_base_path, out_path) => {
// setup for requirejs
const ui_path = path.relative(base_out_path,
path.join(script_base_path, 'app', 'ui'));
return writeFile(out_path, `requirejs(["${ui_path}"], (ui) => {});`)
.then(() => {
console.log(`Please place RequireJS in ${path.join(script_base_path, 'require.js')}`);
const require_path = path.relative(base_out_path,
path.join(script_base_path, 'require.js'));
return [ require_path ];
});
},
noCopyOverride: () => {},
},
'commonjs': {
optionsOverride: (opts) => {
// CommonJS supports properly shifting the default export to work as normal
opts.plugins.unshift("add-module-exports");
},
appWriter: (base_out_path, script_base_path, out_path) => {
const browserify = require('browserify');
const b = browserify(path.join(script_base_path, 'app/ui.js'), {});
return promisify(b.bundle).call(b)
.then(buf => writeFile(out_path, buf))
.then(() => []);
},
noCopyOverride: () => {},
removeModules: true,
},
'systemjs': {
appWriter: (base_out_path, script_base_path, out_path) => {
const ui_path = path.relative(base_out_path,
path.join(script_base_path, 'app', 'ui.js'));
return writeFile(out_path, `SystemJS.import("${ui_path}");`)
.then(() => {
console.log(`Please place SystemJS in ${path.join(script_base_path, 'system-production.js')}`);
// FIXME: Should probably be in the legacy directory
const promise_path = path.relative(base_out_path,
path.join(base_out_path, 'vendor', 'promise.js'));
const systemjs_path = path.relative(base_out_path,
path.join(script_base_path, 'system-production.js'));
return [ promise_path, systemjs_path ];
});
},
noCopyOverride: (paths, no_copy_files) => {
no_copy_files.delete(path.join(paths.vendor, 'promise.js'));
},
},
'umd': {
optionsOverride: (opts) => {
// umd supports properly shifting the default export to work as normal
opts.plugins.unshift("add-module-exports");
},
},
};