diff options
Diffstat (limited to 'html/src/components/zmodem/index.tsx')
-rw-r--r-- | html/src/components/zmodem/index.tsx | 209 |
1 files changed, 0 insertions, 209 deletions
diff --git a/html/src/components/zmodem/index.tsx b/html/src/components/zmodem/index.tsx deleted file mode 100644 index f0e9e87..0000000 --- a/html/src/components/zmodem/index.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import { bind } from 'decko'; -import { h, Component } from 'preact'; -import { saveAs } from 'file-saver'; -import { IDisposable, ITerminalAddon, Terminal } from 'xterm'; -import * as Zmodem from 'zmodem.js/src/zmodem_browser'; - -import { Modal } from '../modal'; - -export interface FlowControl { - limit: number; - highWater: number; - lowWater: number; - - pause: () => void; - resume: () => void; -} - -interface Props { - sender: (data: ArrayLike<number>) => void; - control: FlowControl; -} - -interface State { - modal: boolean; -} - -export class ZmodemAddon extends Component<Props, State> implements ITerminalAddon { - private terminal: Terminal | undefined; - private keyDispose: IDisposable | undefined; - private sentry: Zmodem.Sentry; - private session: Zmodem.Session; - - private written = 0; - private pending = 0; - - constructor(props: Props) { - super(props); - - this.zmodemInit(); - } - - render(_, { modal }: State) { - return ( - <Modal show={modal}> - <label class="file-label"> - <input onChange={this.sendFile} class="file-input" type="file" multiple /> - <span class="file-cta">Choose files…</span> - </label> - </Modal> - ); - } - - activate(terminal: Terminal): void { - this.terminal = terminal; - } - - dispose(): void {} - - consume(data: ArrayBuffer) { - const { sentry, handleError } = this; - try { - sentry.consume(data); - } catch (e) { - handleError(e, 'consume'); - } - } - - @bind - private handleError(e: Error, reason: string) { - console.error(`[ttyd] zmodem ${reason}: `, e); - this.zmodemReset(); - } - - @bind - private zmodemInit() { - this.session = null; - this.sentry = new Zmodem.Sentry({ - to_terminal: (octets: ArrayBuffer) => this.zmodemWrite(octets), - sender: (octets: ArrayLike<number>) => this.zmodemSend(octets), - on_retract: () => this.zmodemReset(), - on_detect: (detection: Zmodem.Detection) => this.zmodemDetect(detection), - }); - } - - @bind - private zmodemReset() { - this.terminal.options.disableStdin = false; - - if (this.keyDispose) { - this.keyDispose.dispose(); - this.keyDispose = null; - } - this.zmodemInit(); - - this.terminal.focus(); - } - - @bind - private zmodemWrite(data: ArrayBuffer): void { - const { limit, highWater, lowWater, pause, resume } = this.props.control; - const { terminal } = this; - const rawData = new Uint8Array(data); - - this.written += rawData.length; - if (this.written > limit) { - terminal.write(rawData, () => { - this.pending = Math.max(this.pending - 1, 0); - if (this.pending < lowWater) { - resume(); - } - }); - this.pending++; - this.written = 0; - if (this.pending > highWater) { - pause(); - } - } else { - terminal.write(rawData); - } - } - - @bind - private zmodemSend(data: ArrayLike<number>): void { - this.props.sender(data); - } - - @bind - private zmodemDetect(detection: Zmodem.Detection): void { - const { terminal, receiveFile, zmodemReset } = this; - terminal.options.disableStdin = true; - - this.keyDispose = terminal.onKey(e => { - const event = e.domEvent; - if (event.ctrlKey && event.key === 'c') { - detection.deny(); - } - }); - - this.session = detection.confirm(); - this.session.on('session_end', zmodemReset); - - if (this.session.type === 'send') { - this.setState({ modal: true }); - } else { - receiveFile(); - } - } - - @bind - private sendFile(event: Event) { - this.setState({ modal: false }); - - const { session, writeProgress, handleError } = this; - const files: FileList = (event.target as HTMLInputElement).files; - - Zmodem.Browser.send_files(session, files, { - on_progress: (_, offer: Zmodem.Offer) => writeProgress(offer), - }) - .then(() => session.close()) - .catch(e => handleError(e, 'send')); - } - - @bind - private receiveFile() { - const { session, writeProgress, handleError } = this; - - session.on('offer', (offer: Zmodem.Offer) => { - const fileBuffer = []; - offer.on('input', payload => { - writeProgress(offer); - fileBuffer.push(new Uint8Array(payload)); - }); - offer - .accept() - .then(() => { - const blob = new Blob(fileBuffer, { type: 'application/octet-stream' }); - saveAs(blob, offer.get_details().name); - }) - .catch(e => handleError(e, 'receive')); - }); - - session.start(); - } - - @bind - private writeProgress(offer: Zmodem.Offer) { - const { terminal, bytesHuman } = this; - - const file = offer.get_details(); - const name = file.name; - const size = file.size; - const offset = offer.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]}`; - } -} |