mirror of
https://github.com/tsl0922/ttyd.git
synced 2025-12-22 20:04:19 +01:00
ZMODEM support added 🎉
This commit is contained in:
4
html/.gitignore
vendored
4
html/.gitignore
vendored
@@ -44,4 +44,6 @@ jspm_packages
|
|||||||
*.tgz
|
*.tgz
|
||||||
|
|
||||||
# Yarn Integrity file
|
# Yarn Integrity file
|
||||||
.yarn-integrity
|
.yarn-integrity
|
||||||
|
|
||||||
|
js/bundle.js
|
||||||
@@ -13,51 +13,52 @@ html, body {
|
|||||||
background-color: #101010;
|
background-color: #101010;
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal {
|
.xterm {
|
||||||
background-color: #101010;
|
background-color: #101010;
|
||||||
color: #f0f0f0;
|
color: #f0f0f0;
|
||||||
font-size: 10pt;
|
font-size: 13px;
|
||||||
font-family: "Menlo for Powerline", Menlo,Consolas,"DejaVu Sans Mono","Liberation Mono",Courier,monospace;
|
font-family: "Menlo for Powerline", Menlo, Consolas, "Liberation Mono", Courier, monospace;
|
||||||
font-variant-ligatures: none;
|
font-variant-ligatures: none;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal .xterm-viewport {
|
.xterm .xterm-viewport {
|
||||||
background-color: rgba(121, 121, 121, 0);
|
background-color: rgba(121, 121, 121, 0);
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text;
|
||||||
-webkit-text-fill-color: transparent;
|
-webkit-text-fill-color: transparent;
|
||||||
transition: background-color 800ms linear;
|
transition: background-color 800ms linear;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal .xterm-viewport::-webkit-scrollbar {
|
.xterm .xterm-viewport::-webkit-scrollbar {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal .xterm-viewport::-webkit-scrollbar-track {
|
.xterm .xterm-viewport::-webkit-scrollbar-track {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal .xterm-viewport::-webkit-scrollbar-thumb {
|
.xterm .xterm-viewport::-webkit-scrollbar-thumb {
|
||||||
background-color: rgba(121, 121, 121, 0.4);
|
background-color: rgba(121, 121, 121, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal .xterm-viewport::-webkit-scrollbar-thumb:hover {
|
.xterm .xterm-viewport::-webkit-scrollbar-thumb:hover {
|
||||||
transition: opacity 0ms linear;
|
transition: opacity 0ms linear;
|
||||||
background-color: rgba(100, 100, 100, .7);
|
background-color: rgba(100, 100, 100, .7);
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal .xterm-viewport::-webkit-scrollbar-thumb:window-inactive {
|
.xterm .xterm-viewport::-webkit-scrollbar-thumb:window-inactive {
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal .terminal-cursor {
|
.xterm .terminal-cursor {
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
color: #101010;
|
color: #101010;
|
||||||
opacity: .7;
|
opacity: .7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal:not(.focus) .terminal-cursor {
|
.xterm:not(.focus) .terminal-cursor {
|
||||||
outline: 1px solid #f0f0f0;
|
outline: 1px solid #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,3 +72,42 @@ html, body {
|
|||||||
color: #f0f0f0;
|
color: #f0f0f0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#modal strong {
|
||||||
|
color: #268bd2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#modal span {
|
||||||
|
color: #2aa198;
|
||||||
|
}
|
||||||
|
|
||||||
|
#modal header {
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#choose .file-name {
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px dashed #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress progress {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress {
|
||||||
|
color: #93a1a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress span {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
@@ -1,8 +1,21 @@
|
|||||||
var gulp = require('gulp'),
|
var gulp = require('gulp'),
|
||||||
|
fs = require("fs"),
|
||||||
|
browserify = require('browserify'),
|
||||||
inlinesource = require('gulp-inline-source');
|
inlinesource = require('gulp-inline-source');
|
||||||
|
|
||||||
gulp.task('inlinesource', function () {
|
gulp.task('browserify', function () {
|
||||||
return gulp.src('*.html')
|
return browserify('./js/app.js')
|
||||||
|
.transform("babelify", {
|
||||||
|
presets: ["env"],
|
||||||
|
global: true,
|
||||||
|
ignore: /\/node_modules\/(?!zmodem.js\/)/
|
||||||
|
})
|
||||||
|
.bundle()
|
||||||
|
.pipe(fs.createWriteStream("./js/bundle.js"));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('inlinesource', ['browserify'], function () {
|
||||||
|
return gulp.src('index.html')
|
||||||
.pipe(inlinesource())
|
.pipe(inlinesource())
|
||||||
.pipe(gulp.dest('../src'));
|
.pipe(gulp.dest('../src'));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,15 +5,49 @@
|
|||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
<title>ttyd - Terminal</title>
|
<title>ttyd - Terminal</title>
|
||||||
<link inline rel="icon" type="image/png" href="favicon.png">
|
<link inline rel="icon" type="image/png" href="favicon.png">
|
||||||
<link inline href="node_modules/xterm/dist/xterm.css">
|
<link inline href="node_modules/bulma/css/bulma.css">
|
||||||
|
<link inline href="node_modules/xterm/src/xterm.css">
|
||||||
<link inline href="css/app.css">
|
<link inline href="css/app.css">
|
||||||
<script inline src="node_modules/xterm/dist/xterm.js"></script>
|
|
||||||
<script inline src="node_modules/xterm/dist/addons/fit/fit.js"></script>
|
|
||||||
<script inline src="js/overlay.js"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="terminal-container"></div>
|
<div id="terminal-container"></div>
|
||||||
|
<div class="modal" id="modal">
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="box">
|
||||||
|
<header id="header"></header>
|
||||||
|
<div id="file">
|
||||||
|
<div><strong>Name: </strong><span id="name"></span></div>
|
||||||
|
<div><strong>Size: </strong><span id="size"></span></div>
|
||||||
|
<div><strong>Last modified: </strong><span id="mtime"></span></div>
|
||||||
|
<div><strong>Mode: </strong><span id="mode"></span></div>
|
||||||
|
<br>
|
||||||
|
<div><strong>Files remaining: </strong><span id="files-remaining"></span></div>
|
||||||
|
<div><strong>Bytes remaining: </strong><span id="bytes-remaining"></span></div>
|
||||||
|
</div>
|
||||||
|
<div id="choose" class="file has-name is-fullwidth">
|
||||||
|
<label class="file-label">
|
||||||
|
<input id="files" class="file-input" type="file" multiple>
|
||||||
|
<span class="file-cta">
|
||||||
|
<strong class="file-label">
|
||||||
|
Choose file(s)…
|
||||||
|
</strong>
|
||||||
|
</span>
|
||||||
|
<span id="file-names" class="file-name"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div id="progress">
|
||||||
|
<progress id="progress-bar" class="progress is-link" max="100"></progress>
|
||||||
|
<p id="progress-info">
|
||||||
|
<span id="bytes-received">-</span>/<span id="bytes-file">-</span> (<span id="percent-received"></span>)
|
||||||
|
transferred
|
||||||
|
<a id="skip" class="button is-link is-small is-pulled-right">Skip</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<script src="auth_token.js"></script>
|
<script src="auth_token.js"></script>
|
||||||
<script inline src="js/app.js"></script>
|
<script inline src="js/bundle.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
380
html/js/app.js
380
html/js/app.js
@@ -1,117 +1,291 @@
|
|||||||
(function() {
|
var Zmodem = require('zmodem.js/src/zmodem_browser');
|
||||||
var terminalContainer = document.getElementById('terminal-container'),
|
var Terminal = require('xterm').Terminal;
|
||||||
httpsEnabled = window.location.protocol === "https:",
|
|
||||||
url = (httpsEnabled ? 'wss://' : 'ws://') + window.location.host + window.location.pathname + 'ws',
|
|
||||||
authToken = (typeof tty_auth_token !== 'undefined') ? tty_auth_token : null,
|
|
||||||
protocols = ["tty"],
|
|
||||||
autoReconnect = -1,
|
|
||||||
term, pingTimer, wsError;
|
|
||||||
|
|
||||||
var openWs = function() {
|
require('xterm/lib/addons/fit');
|
||||||
var ws = new WebSocket(url, protocols),
|
require('./overlay');
|
||||||
textDecoder = new TextDecoder(),
|
|
||||||
textEncoder = new TextEncoder();
|
function showReceiveModal(xfer) {
|
||||||
var unloadCallback = function(event) {
|
resetModal('Receiving files');
|
||||||
var message = 'Close terminal? this will also terminate the command.';
|
var fileInfo = xfer.get_details();
|
||||||
(event || window.event).returnValue = message;
|
document.getElementById('name').textContent = fileInfo.name;
|
||||||
return message;
|
document.getElementById('size').textContent = bytesHuman(fileInfo.size, 2);
|
||||||
};
|
document.getElementById('mtime').textContent = fileInfo.mtime;
|
||||||
var sendMessage = function (msg) {
|
document.getElementById('files-remaining').textContent = fileInfo.files_remaining;
|
||||||
if (ws.readyState === WebSocket.OPEN) {
|
document.getElementById('bytes-remaining').textContent = bytesHuman(fileInfo.bytes_remaining, 2);
|
||||||
ws.send(textEncoder.encode(msg));
|
document.getElementById('mode').textContent = '0' + fileInfo.mode.toString(8);
|
||||||
|
document.getElementById('choose').style.display = 'none';
|
||||||
|
document.getElementById('file').style.display = '';
|
||||||
|
var skip = document.getElementById('skip');
|
||||||
|
skip.disabled = false;
|
||||||
|
skip.onclick = function () {
|
||||||
|
this.disabled = true;
|
||||||
|
xfer.skip();
|
||||||
|
};
|
||||||
|
skip.style.display = '';
|
||||||
|
document.getElementById('modal').classList.add('is-active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSendModal(callback) {
|
||||||
|
resetModal('Sending files');
|
||||||
|
document.getElementById('file').style.display = 'none';
|
||||||
|
document.getElementById('skip').style.display = 'none';
|
||||||
|
document.getElementById('choose').style.display = '';
|
||||||
|
var filesInput = document.getElementById('files');
|
||||||
|
filesInput.disabled = false;
|
||||||
|
filesInput.value = '';
|
||||||
|
filesInput.onchange = function () {
|
||||||
|
this.disabled = true;
|
||||||
|
var files = this.files;
|
||||||
|
var fileNames = '';
|
||||||
|
for (var i = 0; i < files.length; i++) {
|
||||||
|
if (i === 0) {
|
||||||
|
fileNames = files[i].name;
|
||||||
|
} else {
|
||||||
|
fileNames += ' | ' + files[i].name;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
var sendPing = function() {
|
document.getElementById('file-names').textContent = fileNames;
|
||||||
sendMessage("1");
|
callback(files);
|
||||||
};
|
};
|
||||||
|
document.getElementById('modal').classList.add('is-active');
|
||||||
|
}
|
||||||
|
|
||||||
ws.binaryType = 'arraybuffer';
|
function hideModal() {
|
||||||
|
document.getElementById('modal').classList.remove('is-active');
|
||||||
|
}
|
||||||
|
|
||||||
ws.onopen = function() {
|
function resetModal(title) {
|
||||||
console.log("Websocket connection opened");
|
document.getElementById('header').textContent = title;
|
||||||
wsError = false;
|
document.getElementById('bytes-received').textContent = '-';
|
||||||
sendMessage(JSON.stringify({AuthToken: authToken}));
|
document.getElementById('percent-received').textContent = '-%';
|
||||||
pingTimer = setInterval(sendPing, 30 * 1000);
|
document.getElementById('progress-info').style.display = 'none';
|
||||||
|
var progressBar = document.getElementById('progress-bar');
|
||||||
|
progressBar.textContent = '0%';
|
||||||
|
progressBar.value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof term !== 'undefined') {
|
function updateProgress(xfer) {
|
||||||
term.destroy();
|
var size = xfer.get_details().size;
|
||||||
}
|
var offset = xfer.get_offset();
|
||||||
|
document.getElementById('bytes-received').textContent = bytesHuman(offset, 2);
|
||||||
|
document.getElementById('bytes-file').textContent = bytesHuman(size, 2);
|
||||||
|
|
||||||
term = new Terminal();
|
var percentReceived = (100 * offset / size).toFixed(2);
|
||||||
|
document.getElementById('percent-received').textContent = percentReceived + '%';
|
||||||
|
document.getElementById('progress-info').style.display = '';
|
||||||
|
|
||||||
term.on('resize', function(size) {
|
var progressBar = document.getElementById('progress-bar');
|
||||||
sendMessage("2" + JSON.stringify({columns: size.cols, rows: size.rows}));
|
progressBar.textContent = percentReceived + '%';
|
||||||
setTimeout(function() {
|
progressBar.setAttribute('value', percentReceived);
|
||||||
term.showOverlay(size.cols + 'x' + size.rows);
|
}
|
||||||
}, 500);
|
|
||||||
});
|
|
||||||
|
|
||||||
term.on("data", function(data) {
|
function bytesHuman (bytes, precision) {
|
||||||
sendMessage("0" + data);
|
if (isNaN(parseFloat(bytes)) || !isFinite(bytes)) return '-';
|
||||||
});
|
if (bytes === 0) return 0;
|
||||||
|
if (typeof precision === 'undefined') precision = 1;
|
||||||
|
var units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'],
|
||||||
|
number = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||||
|
return (bytes / Math.pow(1024, Math.floor(number))).toFixed(precision) + ' ' + units[number];
|
||||||
|
}
|
||||||
|
|
||||||
term.on('open', function() {
|
function handleSend(zsession) {
|
||||||
// https://stackoverflow.com/a/27923937/1727928
|
return new Promise(function (res) {
|
||||||
window.addEventListener('resize', function() {
|
showSendModal(function (files) {
|
||||||
clearTimeout(window.resizedFinished);
|
Zmodem.Browser.send_files(
|
||||||
window.resizedFinished = setTimeout(function () {
|
zsession,
|
||||||
term.fit();
|
files,
|
||||||
}, 250);
|
{
|
||||||
});
|
on_progress: function(obj, xfer) {
|
||||||
window.addEventListener('beforeunload', unloadCallback);
|
updateProgress(xfer);
|
||||||
term.fit();
|
},
|
||||||
});
|
on_file_complete: function(obj) {
|
||||||
|
hideModal();
|
||||||
while (terminalContainer.firstChild) {
|
}
|
||||||
terminalContainer.removeChild(terminalContainer.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
term.open(terminalContainer, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onmessage = function(event) {
|
|
||||||
var cmd = String.fromCharCode(new DataView(event.data).getUint8()),
|
|
||||||
data = textDecoder.decode(event.data.slice(1));
|
|
||||||
switch(cmd) {
|
|
||||||
case '0':
|
|
||||||
term.write(data);
|
|
||||||
break;
|
|
||||||
case '1': // pong
|
|
||||||
break;
|
|
||||||
case '2':
|
|
||||||
document.title = data;
|
|
||||||
break;
|
|
||||||
case '3':
|
|
||||||
var preferences = JSON.parse(data);
|
|
||||||
Object.keys(preferences).forEach(function(key) {
|
|
||||||
console.log("Setting " + key + ": " + preferences[key]);
|
|
||||||
term.setOption(key, preferences[key]);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case '4':
|
|
||||||
autoReconnect = JSON.parse(data);
|
|
||||||
console.log("Enabling reconnect: " + autoReconnect + " seconds");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onclose = function(event) {
|
|
||||||
console.log("Websocket connection closed with code: " + event.code);
|
|
||||||
if (term) {
|
|
||||||
term.off('data');
|
|
||||||
term.off('resize');
|
|
||||||
if (!wsError) {
|
|
||||||
term.showOverlay("Connection Closed", null);
|
|
||||||
}
|
}
|
||||||
|
).then(
|
||||||
|
zsession.close.bind(zsession),
|
||||||
|
console.error.bind(console)
|
||||||
|
).then(function () {
|
||||||
|
hideModal();
|
||||||
|
res();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleReceive(zsession) {
|
||||||
|
zsession.on('offer', function (xfer) {
|
||||||
|
showReceiveModal(xfer);
|
||||||
|
var fileBuffer = [];
|
||||||
|
xfer.on('input', function (payload) {
|
||||||
|
updateProgress(xfer);
|
||||||
|
fileBuffer.push(new Uint8Array(payload));
|
||||||
|
});
|
||||||
|
xfer.accept().then(function () {
|
||||||
|
Zmodem.Browser.save_to_disk(
|
||||||
|
fileBuffer,
|
||||||
|
xfer.get_details().name
|
||||||
|
);
|
||||||
|
}, console.error.bind(console));
|
||||||
|
});
|
||||||
|
var promise = new Promise(function (res) {
|
||||||
|
zsession.on('session_end', function () {
|
||||||
|
hideModal();
|
||||||
|
res();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
zsession.start();
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
var terminalContainer = document.getElementById('terminal-container'),
|
||||||
|
httpsEnabled = window.location.protocol === 'https:',
|
||||||
|
url = (httpsEnabled ? 'wss://' : 'ws://') + window.location.host + window.location.pathname + 'ws',
|
||||||
|
textDecoder = new TextDecoder(),
|
||||||
|
textEncoder = new TextEncoder(),
|
||||||
|
authToken = (typeof tty_auth_token !== 'undefined') ? tty_auth_token : null,
|
||||||
|
autoReconnect = -1,
|
||||||
|
term, pingTimer, wsError;
|
||||||
|
|
||||||
|
var openWs = function() {
|
||||||
|
var ws = new WebSocket(url, ['tty']);
|
||||||
|
var sendMessage = function (message) {
|
||||||
|
if (ws.readyState === WebSocket.OPEN) {
|
||||||
|
ws.send(textEncoder.encode(message));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var sendData = function (data) {
|
||||||
|
sendMessage('0' + data);
|
||||||
|
};
|
||||||
|
var unloadCallback = function (event) {
|
||||||
|
var message = 'Close terminal? this will also terminate the command.';
|
||||||
|
(event || window.event).returnValue = message;
|
||||||
|
return message;
|
||||||
|
};
|
||||||
|
var zsentry = new Zmodem.Sentry({
|
||||||
|
to_terminal: function _to_terminal(octets) {
|
||||||
|
var buffer = new Uint8Array(octets).buffer;
|
||||||
|
term.write(textDecoder.decode(buffer));
|
||||||
|
},
|
||||||
|
|
||||||
|
sender: function _ws_sender_func(octets) {
|
||||||
|
var array = new Uint8Array(octets.length + 1);
|
||||||
|
array[0] = '0'.charCodeAt(0);
|
||||||
|
array.set(new Uint8Array(octets), 1);
|
||||||
|
ws.send(array.buffer);
|
||||||
|
},
|
||||||
|
|
||||||
|
on_retract: function _on_retract() {
|
||||||
|
// console.log('on_retract');
|
||||||
|
},
|
||||||
|
|
||||||
|
on_detect: function _on_detect(detection) {
|
||||||
|
term.off('data');
|
||||||
|
var zsession = detection.confirm();
|
||||||
|
var promise = zsession.type === 'send' ? handleSend(zsession) : handleReceive(zsession);
|
||||||
|
promise.catch(console.error.bind(console)).then(function () {
|
||||||
|
hideModal();
|
||||||
|
term.on('data', sendData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.binaryType = 'arraybuffer';
|
||||||
|
|
||||||
|
ws.onopen = function(event) {
|
||||||
|
console.log('Websocket connection opened');
|
||||||
|
wsError = false;
|
||||||
|
sendMessage(JSON.stringify({AuthToken: authToken}));
|
||||||
|
pingTimer = setInterval(function() {
|
||||||
|
sendMessage('1');
|
||||||
|
}, 30 * 1000);
|
||||||
|
|
||||||
|
if (typeof term !== 'undefined') {
|
||||||
|
term.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
term = new Terminal({
|
||||||
|
fontSize: 13,
|
||||||
|
fontFamily: '"Menlo for Powerline", Menlo, Consolas, "Liberation Mono", Courier, monospace'
|
||||||
|
});
|
||||||
|
|
||||||
|
term.on('resize', function(size) {
|
||||||
|
if (ws.readyState === WebSocket.OPEN) {
|
||||||
|
sendMessage('2' + JSON.stringify({columns: size.cols, rows: size.rows}));
|
||||||
}
|
}
|
||||||
window.removeEventListener('beforeunload', unloadCallback);
|
setTimeout(function() {
|
||||||
clearInterval(pingTimer);
|
term.showOverlay(size.cols + 'x' + size.rows);
|
||||||
// 1000: CLOSE_NORMAL
|
}, 500);
|
||||||
if (event.code !== 1000 && autoReconnect > 0) {
|
});
|
||||||
setTimeout(openWs, autoReconnect * 1000);
|
|
||||||
}
|
term.on('data', sendData);
|
||||||
};
|
|
||||||
|
while (terminalContainer.firstChild) {
|
||||||
|
terminalContainer.removeChild(terminalContainer.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
term.open(terminalContainer, true);
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/27923937/1727928
|
||||||
|
window.addEventListener('resize', function() {
|
||||||
|
clearTimeout(window.resizedFinished);
|
||||||
|
window.resizedFinished = setTimeout(function () {
|
||||||
|
term.fit();
|
||||||
|
}, 250);
|
||||||
|
});
|
||||||
|
window.addEventListener('beforeunload', unloadCallback);
|
||||||
|
term.fit();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ws.onmessage = function(event) {
|
||||||
|
var cmd = String.fromCharCode(new DataView(event.data).getUint8()),
|
||||||
|
data = event.data.slice(1);
|
||||||
|
switch(cmd) {
|
||||||
|
case '0':
|
||||||
|
zsentry.consume(data);
|
||||||
|
break;
|
||||||
|
case '1': // pong
|
||||||
|
break;
|
||||||
|
case '2':
|
||||||
|
document.title = textDecoder.decode(data);
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
var preferences = JSON.parse(textDecoder.decode(data));
|
||||||
|
Object.keys(preferences).forEach(function(key) {
|
||||||
|
console.log('Setting ' + key + ': ' + preferences[key]);
|
||||||
|
term.setOption(key, preferences[key]);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case '4':
|
||||||
|
autoReconnect = JSON.parse(textDecoder.decode(data));
|
||||||
|
console.log('Enabling reconnect: ' + autoReconnect + ' seconds');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log('Unknown command: ' + cmd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = function(event) {
|
||||||
|
console.log('Websocket connection closed with code: ' + event.code);
|
||||||
|
if (term) {
|
||||||
|
term.off('data');
|
||||||
|
term.off('resize');
|
||||||
|
if (!wsError) {
|
||||||
|
term.showOverlay('Connection Closed', null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.removeEventListener('beforeunload', unloadCallback);
|
||||||
|
clearInterval(pingTimer);
|
||||||
|
// 1000: CLOSE_NORMAL
|
||||||
|
if (event.code !== 1000 && autoReconnect > 0) {
|
||||||
|
setTimeout(openWs, autoReconnect * 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (document.readyState === 'complete' || document.readyState !== 'loading') {
|
||||||
openWs();
|
openWs();
|
||||||
})();
|
} else {
|
||||||
|
document.addEventListener('DOMContentLoaded', openWs);
|
||||||
|
}
|
||||||
@@ -1,58 +1,68 @@
|
|||||||
// ported from hterm.Terminal.prototype.showOverlay
|
// ported from hterm.Terminal.prototype.showOverlay
|
||||||
// https://chromium.googlesource.com/apps/libapps/+/master/hterm/js/hterm_terminal.js
|
// https://chromium.googlesource.com/apps/libapps/+/master/hterm/js/hterm_terminal.js
|
||||||
|
|
||||||
Terminal.prototype.showOverlay = function(msg, timeout) {
|
(function (overlay) {
|
||||||
if (!this.overlayNode_) {
|
module.exports = overlay(require('xterm').Terminal);
|
||||||
if (!this.element)
|
})(function (Terminal) {
|
||||||
|
var exports = {};
|
||||||
|
|
||||||
|
exports.showOverlay = function(msg, timeout) {
|
||||||
|
if (!this.overlayNode_) {
|
||||||
|
if (!this.element)
|
||||||
|
return;
|
||||||
|
this.overlayNode_ = document.createElement('div');
|
||||||
|
this.overlayNode_.style.cssText = (
|
||||||
|
'border-radius: 15px;' +
|
||||||
|
'font-size: xx-large;' +
|
||||||
|
'opacity: 0.75;' +
|
||||||
|
'padding: 0.2em 0.5em 0.2em 0.5em;' +
|
||||||
|
'position: absolute;' +
|
||||||
|
'-webkit-user-select: none;' +
|
||||||
|
'-webkit-transition: opacity 180ms ease-in;' +
|
||||||
|
'-moz-user-select: none;' +
|
||||||
|
'-moz-transition: opacity 180ms ease-in;');
|
||||||
|
|
||||||
|
this.overlayNode_.addEventListener('mousedown', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
this.overlayNode_.style.color = "#101010";
|
||||||
|
this.overlayNode_.style.backgroundColor = "#f0f0f0";
|
||||||
|
|
||||||
|
this.overlayNode_.textContent = msg;
|
||||||
|
this.overlayNode_.style.opacity = '0.75';
|
||||||
|
|
||||||
|
if (!this.overlayNode_.parentNode)
|
||||||
|
this.element.appendChild(this.overlayNode_);
|
||||||
|
|
||||||
|
var divSize = this.element.getBoundingClientRect();
|
||||||
|
var overlaySize = this.overlayNode_.getBoundingClientRect();
|
||||||
|
|
||||||
|
this.overlayNode_.style.top =
|
||||||
|
(divSize.height - overlaySize.height) / 2 + 'px';
|
||||||
|
this.overlayNode_.style.left = (divSize.width - overlaySize.width) / 2 + 'px';
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (this.overlayTimeout_)
|
||||||
|
clearTimeout(this.overlayTimeout_);
|
||||||
|
|
||||||
|
if (timeout === null)
|
||||||
return;
|
return;
|
||||||
this.overlayNode_ = document.createElement('div');
|
|
||||||
this.overlayNode_.style.cssText = (
|
|
||||||
'border-radius: 15px;' +
|
|
||||||
'font-size: xx-large;' +
|
|
||||||
'opacity: 0.75;' +
|
|
||||||
'padding: 0.2em 0.5em 0.2em 0.5em;' +
|
|
||||||
'position: absolute;' +
|
|
||||||
'-webkit-user-select: none;' +
|
|
||||||
'-webkit-transition: opacity 180ms ease-in;' +
|
|
||||||
'-moz-user-select: none;' +
|
|
||||||
'-moz-transition: opacity 180ms ease-in;');
|
|
||||||
|
|
||||||
this.overlayNode_.addEventListener('mousedown', function(e) {
|
this.overlayTimeout_ = setTimeout(function() {
|
||||||
e.preventDefault();
|
self.overlayNode_.style.opacity = '0';
|
||||||
e.stopPropagation();
|
self.overlayTimeout_ = setTimeout(function() {
|
||||||
}, true);
|
if (self.overlayNode_.parentNode)
|
||||||
}
|
self.overlayNode_.parentNode.removeChild(self.overlayNode_);
|
||||||
this.overlayNode_.style.color = "#101010";
|
self.overlayTimeout_ = null;
|
||||||
this.overlayNode_.style.backgroundColor = "#f0f0f0";
|
self.overlayNode_.style.opacity = '0.75';
|
||||||
|
}, 200);
|
||||||
|
}, timeout || 1500);
|
||||||
|
};
|
||||||
|
|
||||||
this.overlayNode_.textContent = msg;
|
Terminal.prototype.showOverlay = exports.showOverlay;
|
||||||
this.overlayNode_.style.opacity = '0.75';
|
|
||||||
|
|
||||||
if (!this.overlayNode_.parentNode)
|
return exports;
|
||||||
this.element.appendChild(this.overlayNode_);
|
});
|
||||||
|
|
||||||
var divSize = this.element.getBoundingClientRect();
|
|
||||||
var overlaySize = this.overlayNode_.getBoundingClientRect();
|
|
||||||
|
|
||||||
this.overlayNode_.style.top =
|
|
||||||
(divSize.height - overlaySize.height) / 2 + 'px';
|
|
||||||
this.overlayNode_.style.left = (divSize.width - overlaySize.width) / 2 + 'px';
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (this.overlayTimeout_)
|
|
||||||
clearTimeout(this.overlayTimeout_);
|
|
||||||
|
|
||||||
if (timeout === null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.overlayTimeout_ = setTimeout(function() {
|
|
||||||
self.overlayNode_.style.opacity = '0';
|
|
||||||
self.overlayTimeout_ = setTimeout(function() {
|
|
||||||
if (self.overlayNode_.parentNode)
|
|
||||||
self.overlayNode_.parentNode.removeChild(self.overlayNode_);
|
|
||||||
self.overlayTimeout_ = null;
|
|
||||||
self.overlayNode_.style.opacity = '0.75';
|
|
||||||
}, 200);
|
|
||||||
}, timeout || 1500);
|
|
||||||
};
|
|
||||||
@@ -13,8 +13,14 @@
|
|||||||
"build": "gulp"
|
"build": "gulp"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"babel-core": "^6.26.0",
|
||||||
|
"babel-preset-env": "^1.6.1",
|
||||||
|
"babelify": "^8.0.0",
|
||||||
|
"browserify": "^14.5.0",
|
||||||
|
"bulma": "^0.6.1",
|
||||||
"gulp": "^3.9.1",
|
"gulp": "^3.9.1",
|
||||||
"gulp-inline-source": "^3.0.0",
|
"gulp-inline-source": "^3.0.0",
|
||||||
"xterm": "^2.9.2"
|
"xterm": "Tyriar/xterm.js#vscode-release/1.19",
|
||||||
|
"zmodem.js": "^0.1.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1447
html/yarn.lock
1447
html/yarn.lock
File diff suppressed because it is too large
Load Diff
57
src/index.html
vendored
57
src/index.html
vendored
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user