mirror of https://github.com/coder/code-server.git
110 lines
2.8 KiB
TypeScript
110 lines
2.8 KiB
TypeScript
import * as crypto from "crypto";
|
|
import * as net from "net";
|
|
|
|
import { VSBuffer } from "vs/base/common/buffer";
|
|
import { NodeSocket, WebSocketNodeSocket } from "vs/base/parts/ipc/node/ipc.net";
|
|
import { PersistentProtocol } from "vs/base/parts/ipc/common/ipc.net";
|
|
import { AuthRequest, ConnectionTypeRequest, HandshakeMessage } from "vs/platform/remote/common/remoteAgentConnection";
|
|
|
|
export interface SocketOptions {
|
|
readonly reconnectionToken: string;
|
|
readonly reconnection: boolean;
|
|
readonly skipWebSocketFrames: boolean;
|
|
}
|
|
|
|
export class Protocol extends PersistentProtocol {
|
|
private disposed: boolean = false;
|
|
|
|
public constructor(
|
|
secWebsocketKey: string,
|
|
socket: net.Socket,
|
|
public readonly options: SocketOptions,
|
|
) {
|
|
super(
|
|
options.skipWebSocketFrames
|
|
? new NodeSocket(socket)
|
|
: new WebSocketNodeSocket(new NodeSocket(socket)),
|
|
);
|
|
socket.on("error", () => this.dispose());
|
|
socket.on("end", () => this.dispose());
|
|
|
|
// This magic value is specified by the websocket spec.
|
|
const magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
const reply = crypto.createHash("sha1")
|
|
.update(secWebsocketKey + magic)
|
|
.digest("base64");
|
|
|
|
socket.write([
|
|
"HTTP/1.1 101 Switching Protocols",
|
|
"Upgrade: websocket",
|
|
"Connection: Upgrade",
|
|
`Sec-WebSocket-Accept: ${reply}`,
|
|
].join("\r\n") + "\r\n\r\n");
|
|
}
|
|
|
|
public sendDisconnect(): void {
|
|
if (!this.disposed) {
|
|
super.sendDisconnect();
|
|
}
|
|
}
|
|
|
|
public dispose(error?: Error): void {
|
|
if (!this.disposed) {
|
|
this.disposed = true;
|
|
if (error) {
|
|
this.sendMessage({ type: "error", reason: error.message });
|
|
}
|
|
super.dispose();
|
|
this.getSocket().dispose();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Perform a handshake to get a connection request.
|
|
*/
|
|
public handshake(): Promise<ConnectionTypeRequest> {
|
|
return new Promise((resolve, reject) => {
|
|
const handler = this.onControlMessage((rawMessage) => {
|
|
try {
|
|
const message = JSON.parse(rawMessage.toString());
|
|
switch (message.type) {
|
|
case "auth": return this.authenticate(message);
|
|
case "connectionType":
|
|
handler.dispose();
|
|
return resolve(message);
|
|
default: throw new Error("Unrecognized message type");
|
|
}
|
|
} catch (error) {
|
|
handler.dispose();
|
|
reject(error);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* TODO: This ignores the authentication process entirely for now.
|
|
*/
|
|
private authenticate(_message: AuthRequest): void {
|
|
this.sendMessage({
|
|
type: "sign",
|
|
data: "",
|
|
});
|
|
}
|
|
|
|
/**
|
|
* TODO: implement.
|
|
*/
|
|
public tunnel(): void {
|
|
throw new Error("Tunnel is not implemented yet");
|
|
}
|
|
|
|
/**
|
|
* Send a handshake message. In the case of the extension host, it just sends
|
|
* back a debug port.
|
|
*/
|
|
public sendMessage(message: HandshakeMessage | { debugPort?: number } ): void {
|
|
this.sendControl(VSBuffer.fromString(JSON.stringify(message)));
|
|
}
|
|
}
|