{
window.removeEventListener('beforeunload', this.onWindowUnload);
}
- render({ id }: Props, { modal }: State) {
+ render({ id }: Props) {
return (
(this.container = c)}>
-
-
-
+ (this.zmodemAddon = c)} sender={this.sendData} />
);
}
@bind
- private zmodemWrite(data: ArrayBuffer): void {
- const { terminal } = this;
- terminal.writeUtf8(new Uint8Array(data));
- }
-
- @bind
- private zmodemSend(data: ArrayLike): void {
+ private sendData(data: ArrayLike) {
const { socket } = this;
const payload = new Uint8Array(data.length + 1);
payload[0] = Command.INPUT.charCodeAt(0);
@@ -115,78 +84,6 @@ export class Xterm extends Component {
socket.send(payload);
}
- @bind
- private zmodemDetect(detection: Zmodem.Detection): void {
- const { terminal, receiveFile } = this;
- terminal.setOption('disableStdin', true);
- this.detection = detection;
- this.session = detection.confirm();
-
- if (this.session.type === 'send') {
- this.setState({ modal: true });
- } else {
- receiveFile();
- }
- }
-
- @bind
- private sendFile(event: Event) {
- this.setState({ modal: false });
-
- const { terminal, session, writeProgress } = this;
- const files: FileList = (event.target as HTMLInputElement).files;
-
- Zmodem.Browser.send_files(session, files, {
- on_progress: (_, xfer: any) => writeProgress(xfer),
- })
- .then(() => {
- session.close();
- this.detection = null;
- terminal.setOption('disableStdin', false);
- })
- .catch(e => {
- console.log(`[ttyd] zmodem send: `, e);
- });
- }
-
- @bind
- private receiveFile() {
- const { terminal, session, writeProgress } = this;
-
- session.on('offer', (xfer: any) => {
- const fileBuffer = [];
- xfer.on('input', payload => {
- writeProgress(xfer);
- fileBuffer.push(new Uint8Array(payload));
- });
- xfer.accept().then(() => {
- Zmodem.Browser.save_to_disk(fileBuffer, xfer.get_details().name);
- });
- });
-
- session.on('session_end', () => {
- this.detection = null;
- terminal.setOption('disableStdin', false);
- });
-
- session.start();
- }
-
- @bind
- private writeProgress(xfer: any) {
- const { terminal, bytesHuman } = this;
-
- const file = xfer.get_details();
- const name = file.name;
- const size = file.size;
- const offset = xfer.get_offset();
- const percent = ((100 * offset) / size).toFixed(2);
-
- terminal.write(
- `${name} ${percent}% ${bytesHuman(offset, 2)}/${bytesHuman(size, 2)}\r`
- );
- }
-
@bind
private onWindowResize() {
const { fitAddon } = this;
@@ -219,6 +116,7 @@ export class Xterm extends Component {
terminal.loadAddon(fitAddon);
terminal.loadAddon(overlayAddon);
terminal.loadAddon(new WebLinksAddon());
+ terminal.loadAddon(this.zmodemAddon);
terminal.onTitleChange(data => {
if (data && data !== '') {
@@ -273,23 +171,14 @@ export class Xterm extends Component {
@bind
private onSocketData(event: MessageEvent) {
- const { terminal, textDecoder } = this;
+ const { terminal, textDecoder, zmodemAddon } = this;
const rawData = event.data as ArrayBuffer;
const cmd = String.fromCharCode(new Uint8Array(rawData)[0]);
const data = rawData.slice(1);
switch (cmd) {
case Command.OUTPUT:
- try {
- this.sentry.consume(data);
- } catch (e) {
- console.log(`[ttyd] zmodem consume: `, e);
- terminal.setOption('disableStdin', false);
- if (this.detection) {
- this.detection.deny();
- this.detection = null;
- }
- }
+ zmodemAddon.consume(data);
break;
case Command.SET_WINDOW_TITLE:
this.title = textDecoder.decode(data);
@@ -331,16 +220,4 @@ export class Xterm extends Component {
socket.send(textEncoder.encode(Command.INPUT + data));
}
}
-
- private bytesHuman(bytes: any, precision: number): string {
- if (!/^([-+])?|(\.\d+)(\d+(\.\d+)?|(\d+\.)|Infinity)$/.test(bytes)) {
- return '-';
- }
- if (bytes === 0) return '0';
- if (typeof precision === 'undefined') precision = 1;
- const units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
- const num = Math.floor(Math.log(bytes) / Math.log(1024));
- const value = (bytes / Math.pow(1024, Math.floor(num))).toFixed(precision);
- return `${value} ${units[num]}`;
- }
}
diff --git a/html/src/components/zmodem/index.tsx b/html/src/components/zmodem/index.tsx
new file mode 100644
index 0000000..5938d2f
--- /dev/null
+++ b/html/src/components/zmodem/index.tsx
@@ -0,0 +1,162 @@
+import { bind } from 'decko';
+import { Component, h } from 'preact';
+import { ITerminalAddon, Terminal } from 'xterm';
+import * as Zmodem from 'zmodem.js/src/zmodem_browser';
+
+import { Modal } from '../modal';
+
+interface Props {
+ sender: (data: ArrayLike) => void;
+}
+
+interface State {
+ modal: boolean;
+}
+
+export class ZmodemAddon extends Component
+ implements ITerminalAddon {
+ private terminal: Terminal | undefined;
+ private sentry: Zmodem.Sentry;
+ private session: Zmodem.Session;
+
+ constructor(props) {
+ super(props);
+
+ this.sentry = new Zmodem.Sentry({
+ to_terminal: (octets: ArrayBuffer) => this.zmodemWrite(octets),
+ sender: (octets: ArrayLike) => this.zmodemSend(octets),
+ on_retract: () => this.zmodemRetract(),
+ on_detect: (detection: any) => this.zmodemDetect(detection),
+ });
+ }
+
+ render(_, { modal }: State) {
+ return (
+
+
+
+ );
+ }
+
+ activate(terminal: Terminal): void {
+ this.terminal = terminal;
+ }
+
+ dispose(): void {}
+
+ consume(data: ArrayBuffer) {
+ const { sentry, terminal } = this;
+ try {
+ sentry.consume(data);
+ } catch (e) {
+ console.log(`[ttyd] zmodem consume: `, e);
+ terminal.setOption('disableStdin', false);
+ }
+ }
+
+ @bind
+ private zmodemWrite(data: ArrayBuffer): void {
+ this.terminal.writeUtf8(new Uint8Array(data));
+ }
+
+ @bind
+ private zmodemSend(data: ArrayLike): void {
+ this.props.sender(data);
+ }
+
+ @bind
+ private zmodemRetract(): void {
+ this.terminal.setOption('disableStdin', false);
+ }
+
+ @bind
+ private zmodemDetect(detection: Zmodem.Detection): void {
+ const { terminal, receiveFile } = this;
+ terminal.setOption('disableStdin', true);
+ this.session = detection.confirm();
+
+ if (this.session.type === 'send') {
+ this.setState({ modal: true });
+ } else {
+ receiveFile();
+ }
+ }
+
+ @bind
+ private sendFile(event: Event) {
+ this.setState({ modal: false });
+
+ const { terminal, session, writeProgress } = this;
+ const files: FileList = (event.target as HTMLInputElement).files;
+
+ Zmodem.Browser.send_files(session, files, {
+ on_progress: (_, xfer: any) => writeProgress(xfer),
+ })
+ .then(() => {
+ session.close();
+ terminal.setOption('disableStdin', false);
+ })
+ .catch(e => {
+ console.log(`[ttyd] zmodem send: `, e);
+ });
+ }
+
+ @bind
+ private receiveFile() {
+ const { terminal, session, writeProgress } = this;
+
+ session.on('offer', (xfer: any) => {
+ const fileBuffer = [];
+ xfer.on('input', payload => {
+ writeProgress(xfer);
+ fileBuffer.push(new Uint8Array(payload));
+ });
+ xfer.accept().then(() => {
+ Zmodem.Browser.save_to_disk(fileBuffer, xfer.get_details().name);
+ });
+ });
+
+ session.on('session_end', () => {
+ terminal.setOption('disableStdin', false);
+ });
+
+ session.start();
+ }
+
+ @bind
+ private writeProgress(xfer: any) {
+ const { terminal, bytesHuman } = this;
+
+ const file = xfer.get_details();
+ const name = file.name;
+ const size = file.size;
+ const offset = xfer.get_offset();
+ const percent = ((100 * offset) / size).toFixed(2);
+
+ terminal.write(
+ `${name} ${percent}% ${bytesHuman(offset, 2)}/${bytesHuman(size, 2)}\r`
+ );
+ }
+
+ private bytesHuman(bytes: any, precision: number): string {
+ if (!/^([-+])?|(\.\d+)(\d+(\.\d+)?|(\d+\.)|Infinity)$/.test(bytes)) {
+ return '-';
+ }
+ if (bytes === 0) return '0';
+ if (typeof precision === 'undefined') precision = 1;
+ const units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
+ const num = Math.floor(Math.log(bytes) / Math.log(1024));
+ const value = (bytes / Math.pow(1024, Math.floor(num))).toFixed(precision);
+ return `${value} ${units[num]}`;
+ }
+}
diff --git a/src/index.html b/src/index.html
index 4291a0a..de0ee2d 100644
--- a/src/index.html
+++ b/src/index.html
@@ -1 +1 @@
-ttyd - Terminal
\ No newline at end of file
+ttyd - Terminal
\ No newline at end of file