-
Notifications
You must be signed in to change notification settings - Fork 0
/
mod.tsx
149 lines (130 loc) · 4.74 KB
/
mod.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/** @jsx h */
import { serve } from "https://deno.land/[email protected]/http/server.ts";
import html, { h } from "https://deno.land/x/[email protected]/mod.ts";
import { KubeConfig } from "https://deno.land/x/[email protected]/mod.ts";
const serverConfig = await KubeConfig.getInClusterConfig();
const serverCtx = serverConfig.fetchContext();
// change these as needed :)
const namespace = 'code-server';
const podName = 'srv-0';
const containerName = 'srv';
async function startTty(clientWs: WebSocket) {
// Create a passthru stream to buffer client->server traffic
// We already have stuff from the client, but we aren't connected to the server yet
const clientToServer = new TransformStream();
const serverWriter = clientToServer.writable.getWriter();
clientWs.addEventListener('message', evt => {
if (evt.data instanceof ArrayBuffer) {
serverWriter.write(prependChannel(0, new Uint8Array(evt.data)));
} else if (typeof evt.data == 'string') {
const cmd = JSON.parse(evt.data);
switch (cmd.msg) {
case 'resize': {
const json = JSON.stringify({ Width: cmd.size.cols, Height: cmd.size.rows });
serverWriter.write(prependChannel(4, new TextEncoder().encode(json)));
} break;
}
console.log('client command:', cmd);
}
});
const path = `/api/v1/namespaces/${namespace}/pods/${podName}/exec?container=${containerName}&stdin=1&stdout=1&stderr=1&tty=1&command=bash&command=-il`;
const url = new URL(path, serverCtx.cluster?.server);
url.protocol = url.protocol.replace('http', 'ws');
const serverWs = new WebSocketStream(url.toString(), {
headers: {
authorization: await serverCtx.getAuthHeader() ?? '',
},
});
const serverConn = await serverWs.connection;
clientToServer.readable.pipeTo(serverConn.writable);
clientWs.send(JSON.stringify({
msg: 'ready',
}));
for await (const pkt of serverConn.readable) {
if (pkt instanceof Uint8Array) {
clientWs.send(pkt.slice(1));
} else {
console.log({pkt});
}
}
}
function prependChannel(idx: number, chunk: Uint8Array) {
const buf = new ArrayBuffer(chunk.byteLength + 1);
new DataView(buf).setUint8(0, idx);
const array = new Uint8Array(buf);
array.set(chunk, 1);
return array;
}
const handler = (req: Request) => {
const { pathname } = new URL(req.url);
console.log(req.method, pathname);
if (pathname === '/tty') {
const upgrade = req.headers.get("upgrade") ?? "";
if (req.method == 'GET' && upgrade.toLowerCase() == "websocket") {
const { socket, response } = Deno.upgradeWebSocket(req);
startTty(socket);
return response;
}
return new Response('not an upgrade', { status: 400 });
}
if (pathname === '/') return html({
title: "Hello World!",
styles: [
"* { box-sizing: border-box; }",
"html, body { margin: 0; height: 100%; }",
"body { padding: 1em; gap: 1em; background: #86efac; display: flex; flex-direction: column; align-items: center; justify-content: center; }",
"#terminal { flex: 1; padding: 0.5em; background-color: black; border-radius: 10px; width: 100%; }",
],
links: [{
href: 'https://unpkg.dev/[email protected]/css/xterm.css',
rel: 'stylesheet',
}],
scripts: [{
src: 'https://unpkg.dev/[email protected]/lib/xterm.js',
}, {
src: 'https://unpkg.dev/[email protected]/lib/xterm-addon-fit.js',
}, {
text: `
const wsUrl = new URL('/tty', location);
wsUrl.protocol = wsUrl.protocol.replace('http', 'ws');
const term = new Terminal();
const fitAddon = new FitAddon.FitAddon();
term.loadAddon(fitAddon);
addEventListener("DOMContentLoaded", (event) => {
const ws = new WebSocket(wsUrl);
ws.addEventListener('message', async evt => {
if (evt.data instanceof Blob) {
term.write(new Uint8Array(await evt.data.arrayBuffer()));
} else {
console.log('received control payload:', evt.data);
}
});
ws.addEventListener('open', evt => {
term.onData(data => {
ws.send(new TextEncoder().encode(data));
});
term.onResize(size => {
ws.send(JSON.stringify({
msg: 'resize',
size,
}));
});
term.open(document.getElementById('terminal'));
fitAddon.fit();
addEventListener("resize", () => {
fitAddon.fit();
});
});
});
`,
}],
body: (
<body>
<img width="64" height="64" src="https://dash.deno.com/assets/logo.svg" />
<div id="terminal"></div>
</body>
),
});
return new Response('404', { status: 404 });
};
serve(handler);