|
| 1 | +// "wsep" browser client reference implementation |
| 2 | + |
| 3 | +const DELIMITER = '\n'.charCodeAt(0); |
| 4 | + |
| 5 | +// Command describes initialization parameters for a remote command |
| 6 | +export interface Command { |
| 7 | + command: string; |
| 8 | + args?: string[]; |
| 9 | + tty?: boolean; |
| 10 | + uid?: number; |
| 11 | + gid?: number; |
| 12 | + env?: string[]; |
| 13 | + working_dir?: string; |
| 14 | +} |
| 15 | + |
| 16 | +export type ClientHeader = |
| 17 | + | { type: 'start'; command: Command } |
| 18 | + | { type: 'stdin' } |
| 19 | + | { type: 'close_stdin' } |
| 20 | + | { type: 'resize'; cols: number; rows: number }; |
| 21 | + |
| 22 | +export type ServerHeader = |
| 23 | + | { type: 'stdout' } |
| 24 | + | { type: 'stderr' } |
| 25 | + | { type: 'pid'; pid: number } |
| 26 | + | { type: 'exit_code'; exit_code: number }; |
| 27 | + |
| 28 | +export type Header = ClientHeader | ServerHeader; |
| 29 | + |
| 30 | +export const setBinaryType = (ws: WebSocket) => { |
| 31 | + ws.binaryType = 'arraybuffer'; |
| 32 | +}; |
| 33 | + |
| 34 | +export const sendStdin = (ws: WebSocket, data: Uint8Array) => { |
| 35 | + if (data.byteLength < 1) return; |
| 36 | + const msg = joinMessage({ type: 'stdin' }, data); |
| 37 | + ws.send(msg.buffer); |
| 38 | +}; |
| 39 | + |
| 40 | +export const closeStdin = (ws: WebSocket) => { |
| 41 | + const msg = joinMessage({ type: 'close_stdin' }); |
| 42 | + ws.send(msg.buffer); |
| 43 | +}; |
| 44 | + |
| 45 | +export const startCommand = (ws: WebSocket, command: Command) => { |
| 46 | + const msg = joinMessage({ type: 'start', command: command }); |
| 47 | + ws.send(msg.buffer); |
| 48 | +}; |
| 49 | + |
| 50 | +export const parseServerMessage = ( |
| 51 | + ev: MessageEvent |
| 52 | +): [ServerHeader, Uint8Array] => { |
| 53 | + const [header, body] = splitMessage(ev.data); |
| 54 | + return [header as ServerHeader, body]; |
| 55 | +}; |
| 56 | + |
| 57 | +export const resizeTerminal = ( |
| 58 | + ws: WebSocket, |
| 59 | + rows: number, |
| 60 | + cols: number |
| 61 | +): void => { |
| 62 | + const msg = joinMessage({ type: 'resize', cols, rows }); |
| 63 | + ws.send(msg.buffer); |
| 64 | +}; |
| 65 | + |
| 66 | +const joinMessage = (header: ClientHeader, body?: Uint8Array): Uint8Array => { |
| 67 | + const encodedHeader = new TextEncoder().encode(JSON.stringify(header)); |
| 68 | + if (body && body.length > 0) { |
| 69 | + const tmp = new Uint8Array(encodedHeader.byteLength + 1 + body.byteLength); |
| 70 | + tmp.set(encodedHeader, 0); |
| 71 | + tmp.set([DELIMITER], encodedHeader.byteLength); |
| 72 | + tmp.set(body, encodedHeader.byteLength + 1); |
| 73 | + return tmp; |
| 74 | + } |
| 75 | + return encodedHeader; |
| 76 | +}; |
| 77 | + |
| 78 | +const splitMessage = (message: ArrayBuffer): [Header, Uint8Array] => { |
| 79 | + let array: Uint8Array; |
| 80 | + if (typeof message === 'string') { |
| 81 | + array = new TextEncoder().encode(message); |
| 82 | + } else { |
| 83 | + array = new Uint8Array(message); |
| 84 | + } |
| 85 | + |
| 86 | + for (let i = 0; i < array.length; i++) { |
| 87 | + if (array[i] === DELIMITER) { |
| 88 | + const headerText = new TextDecoder().decode(array.slice(0, i)); |
| 89 | + const header: ServerHeader = JSON.parse(headerText); |
| 90 | + const body = |
| 91 | + array.length > i + 1 |
| 92 | + ? array.slice(i + 1, array.length) |
| 93 | + : new Uint8Array(0); |
| 94 | + return [header, body]; |
| 95 | + } |
| 96 | + } |
| 97 | + |
| 98 | + return [JSON.parse(new TextDecoder().decode(array)), new Uint8Array(0)]; |
| 99 | +}; |
0 commit comments