mirror of
https://github.com/yudai/gotty.git
synced 2026-02-15 20:58:06 +01:00
feat(zmodem): Allow file uploads/downloads
Using zmodem (rz and sz commands from lrzsz) you can now send and receive files.
This commit is contained in:
parent
163fd0537c
commit
782991c356
12 changed files with 663 additions and 21 deletions
130
js/src/webtty.ts
130
js/src/webtty.ts
|
|
@ -1,3 +1,5 @@
|
|||
import * as Zmodem from 'zmodem.js/src/zmodem_browser';
|
||||
|
||||
export const protocols = ["webtty"];
|
||||
|
||||
export const msgInputUnknown = '0';
|
||||
|
|
@ -18,6 +20,7 @@ export interface Terminal {
|
|||
info(): { columns: number, rows: number };
|
||||
output(data: string): void;
|
||||
showMessage(message: string, timeout: number): void;
|
||||
getMessage(): HTMLElement;
|
||||
removeMessage(): void;
|
||||
setWindowTitle(title: string): void;
|
||||
setPreferences(value: object): void;
|
||||
|
|
@ -46,10 +49,12 @@ export interface ConnectionFactory {
|
|||
export class WebTTY {
|
||||
term: Terminal;
|
||||
connectionFactory: ConnectionFactory;
|
||||
connection: Connection;
|
||||
args: string;
|
||||
authToken: string;
|
||||
reconnect: number;
|
||||
bufSize: number;
|
||||
sentry: Zmodem.Sentry;
|
||||
|
||||
constructor(term: Terminal, connectionFactory: ConnectionFactory, args: string, authToken: string) {
|
||||
this.term = term;
|
||||
|
|
@ -58,12 +63,126 @@ export class WebTTY {
|
|||
this.authToken = authToken;
|
||||
this.reconnect = -1;
|
||||
this.bufSize = 1024;
|
||||
|
||||
this.sentry = new Zmodem.Sentry({
|
||||
'to_terminal': (d: any) => this.term.output(d),
|
||||
'on_detect': (detection: Zmodem.Detection) => this.zmodemDetect(detection),
|
||||
'sender': (x: Uint8Array) => this.sendInput(x),
|
||||
'on_retract': (x: any) => alert("never mind!"),
|
||||
})
|
||||
};
|
||||
|
||||
private zmodemDetect(detection: Zmodem.Detection) {
|
||||
var zsession = detection.confirm();
|
||||
|
||||
if (zsession.type === "send") {
|
||||
this.zmodemSend(zsession);
|
||||
}
|
||||
else {
|
||||
zsession.on("offer", (xfer: any) => this.zmodemOffer(xfer));
|
||||
zsession.start();
|
||||
}
|
||||
}
|
||||
|
||||
private zmodemSend(zsession: any) {
|
||||
let dialog = this.getFileSendDialog();
|
||||
dialog.style.display = 'block';
|
||||
|
||||
let selector = document.getElementById("sendFileSelector");
|
||||
if (selector != null) {
|
||||
selector.onchange = (event) => {
|
||||
Zmodem.Browser.send_files(zsession, (event.target as HTMLInputElement).files)
|
||||
.then(() => zsession.close())
|
||||
.catch(e => console.log(e));
|
||||
dialog.style.display = 'none';
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private zmodemOffer(xfer: Zmodem.Offer) {
|
||||
var dialog = this.getFileAcceptanceDialog();
|
||||
dialog.style.display = 'block';
|
||||
|
||||
var filenameElem = document.getElementById("filename");
|
||||
if (filenameElem != null) {
|
||||
filenameElem.textContent = xfer.get_details().name;
|
||||
}
|
||||
var sizeElem = document.getElementById("filesize");
|
||||
if (sizeElem != null) {
|
||||
sizeElem.textContent = xfer.get_details().size;
|
||||
}
|
||||
var skipLink = document.getElementById("skipTransfer");
|
||||
if (skipLink != null) {
|
||||
skipLink.onclick = (ev) => {
|
||||
xfer.skip();
|
||||
dialog.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
var acceptLink = document.getElementById("acceptTransfer");
|
||||
if (acceptLink != null) {
|
||||
acceptLink.onclick = (ev) => {
|
||||
dialog.style.display = 'none';
|
||||
xfer.accept().then((payloads: any) => {
|
||||
//Now you need some mechanism to save the file.
|
||||
//An example of how you can do this in a browser:
|
||||
Zmodem.Browser.save_to_disk(
|
||||
payloads,
|
||||
xfer.get_details().name
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sendInput(input: string | Uint8Array) {
|
||||
let effectiveBufferSize = this.bufSize - 1;
|
||||
let dataString: string
|
||||
|
||||
if (Array.isArray(input)) {
|
||||
dataString = String.fromCharCode.apply(null, input);
|
||||
} else {
|
||||
dataString = (input as string);
|
||||
}
|
||||
|
||||
// Account for base64 encoding
|
||||
let maxChunkSize = Math.floor(effectiveBufferSize / 4)*3;
|
||||
|
||||
for (let i = 0; i < Math.ceil(dataString.length / maxChunkSize); i++) {
|
||||
let inputChunk = dataString.substring(i * effectiveBufferSize, Math.min((i + 1) * effectiveBufferSize, dataString.length))
|
||||
this.connection.send(msgInput + btoa(inputChunk));
|
||||
}
|
||||
}
|
||||
|
||||
getFileAcceptanceDialog(): HTMLElement {
|
||||
let dialog = document.getElementById("acceptFileDialog");
|
||||
if (dialog == null) {
|
||||
dialog = document.createElement("div");
|
||||
dialog.id = 'acceptFileDialog';
|
||||
dialog.className = 'fileDialog';
|
||||
dialog.innerHTML = '<p>Incoming file transfer: <tt id="filename"></tt> (<span id="filesize"></span> bytes)</p><a id="acceptTransfer" href="#">Accept</a> <a id="skipTransfer" href="#">Decline</a>';
|
||||
document.body.appendChild(dialog);
|
||||
}
|
||||
return dialog;
|
||||
}
|
||||
|
||||
getFileSendDialog(): HTMLElement {
|
||||
let dialog = document.getElementById("sendFileDialog");
|
||||
if (dialog == null) {
|
||||
dialog = document.createElement("div");
|
||||
dialog.id = 'sendFileDialog';
|
||||
dialog.className = 'fileDialog';
|
||||
dialog.innerHTML = '<p>Remote ready to receive files. <input id="sendFileSelector" class="file-input" type="file" multiple="" /></p>';
|
||||
document.body.appendChild(dialog);
|
||||
}
|
||||
return dialog;
|
||||
}
|
||||
|
||||
open() {
|
||||
let connection = this.connectionFactory.create();
|
||||
let pingTimer: NodeJS.Timeout;
|
||||
let reconnectTimeout: NodeJS.Timeout;
|
||||
this.connection = connection;
|
||||
|
||||
const setup = () => {
|
||||
connection.onOpen(() => {
|
||||
|
|
@ -93,14 +212,7 @@ export class WebTTY {
|
|||
|
||||
this.term.onInput(
|
||||
(input: string) => {
|
||||
// Leave room for message type id
|
||||
let effectiveBufferSize = this.bufSize - 1;
|
||||
|
||||
// Split input into buffer sized chunks
|
||||
for (let i = 0; i < Math.ceil(input.length/effectiveBufferSize); i++) {
|
||||
let inputChunk = input.substring(i*effectiveBufferSize, Math.min((i+1)*effectiveBufferSize, input.length))
|
||||
connection.send(msgInput + inputChunk);
|
||||
}
|
||||
this.sendInput(input);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -114,7 +226,7 @@ export class WebTTY {
|
|||
const payload = data.slice(1);
|
||||
switch (data[0]) {
|
||||
case msgOutput:
|
||||
this.term.output(atob(payload));
|
||||
this.sentry.consume(Uint8Array.from(atob(payload), c => c.charCodeAt(0)));
|
||||
break;
|
||||
case msgPong:
|
||||
break;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue