Skip to content

Commit 77b7026

Browse files
authored
Browser client (#8)
Add browser reference implementation
1 parent 5cbe721 commit 77b7026

File tree

1 file changed

+99
-0
lines changed

1 file changed

+99
-0
lines changed

browser/client.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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

Comments
 (0)