forked from coder/code-server
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathprotocol.ts
153 lines (137 loc) · 4.61 KB
/
protocol.ts
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
150
151
152
153
import { field, logger, Logger } from '@coder/logger';
import * as net from 'net';
import { VSBuffer } from 'vs/base/common/buffer';
import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net';
import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { AuthRequest, ConnectionTypeRequest, HandshakeMessage } from 'vs/platform/remote/common/remoteAgentConnection';
export interface SocketOptions {
/** The token is how we identify and connect to existing sessions. */
readonly reconnectionToken: string;
/** Specifies that the client is trying to reconnect. */
readonly reconnection: boolean;
/** If true assume this is not a web socket (always false for code-server). */
readonly skipWebSocketFrames: boolean;
/** Whether to support compression (web socket only). */
readonly permessageDeflate?: boolean;
/**
* Seed zlib with these bytes (web socket only). If parts of inflating was
* done in a different zlib instance we need to pass all those bytes into zlib
* otherwise the inflate might hit an inflated portion referencing a distance
* too far back.
*/
readonly inflateBytes?: VSBuffer;
}
export class Protocol extends PersistentProtocol {
private readonly logger: Logger;
public constructor(socket: net.Socket, public readonly options: SocketOptions) {
super(
options.skipWebSocketFrames
? new NodeSocket(socket)
: new WebSocketNodeSocket(
new NodeSocket(socket),
options.permessageDeflate || false,
options.inflateBytes || null,
// Always record inflate bytes if using permessage-deflate.
options.permessageDeflate || false,
),
);
this.logger = logger.named('protocol', field('token', this.options.reconnectionToken));
}
public getUnderlyingSocket(): net.Socket {
const socket = this.getSocket();
return socket instanceof NodeSocket
? socket.socket
: (socket as WebSocketNodeSocket).socket.socket;
}
/**
* Perform a handshake to get a connection request.
*/
public handshake(): Promise<ConnectionTypeRequest> {
this.logger.debug('Initiating handshake...');
return new Promise((resolve, reject) => {
const cleanup = () => {
handler.dispose();
onClose.dispose();
clearTimeout(timeout);
};
const onClose = this.onSocketClose(() => {
cleanup();
this.logger.debug('Handshake failed');
reject(new Error('Protocol socket closed unexpectedly'));
});
const timeout = setTimeout(() => {
cleanup();
this.logger.debug('Handshake timed out');
reject(new Error('Protocol handshake timed out'));
}, 10000); // Matches the client timeout.
const handler = this.onControlMessage((rawMessage) => {
try {
const raw = rawMessage.toString();
this.logger.trace('Got message', field('message', raw));
const message = JSON.parse(raw);
switch (message.type) {
case 'auth':
return this.authenticate(message);
case 'connectionType':
cleanup();
this.logger.debug('Handshake completed');
return resolve(message);
default:
throw new Error('Unrecognized message type');
}
} catch (error) {
cleanup();
reject(error);
}
});
// Kick off the handshake in case we missed the client's opening shot.
// TODO: Investigate why that message seems to get lost.
this.authenticate();
});
}
/**
* TODO: This ignores the authentication process entirely for now.
*/
private authenticate(_?: 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 should just
* send a debug port.
*/
public sendMessage(message: HandshakeMessage | { debugPort?: number | null } ): void {
this.sendControl(VSBuffer.fromString(JSON.stringify(message)));
}
/**
* Disconnect and dispose everything including the underlying socket.
*/
public destroy(reason?: string): void {
try {
if (reason) {
this.sendMessage({ type: 'error', reason });
}
// If still connected try notifying the client.
this.sendDisconnect();
} catch (error) {
// I think the write might fail if already disconnected.
this.logger.warn(error.message || error);
}
this.dispose(); // This disposes timers and socket event handlers.
this.getSocket().dispose(); // This will destroy() the socket.
}
/**
* Get inflateBytes from the current socket.
*/
public get inflateBytes(): Uint8Array | undefined {
const socket = this.getSocket();
return socket instanceof WebSocketNodeSocket
? socket.recordedInflateBytes.buffer
: undefined;
}
}