@lspeasy/core 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +550 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/jsonrpc/framing.d.ts +32 -0
- package/dist/jsonrpc/framing.d.ts.map +1 -0
- package/dist/jsonrpc/framing.js +96 -0
- package/dist/jsonrpc/framing.js.map +1 -0
- package/dist/jsonrpc/messages.d.ts +64 -0
- package/dist/jsonrpc/messages.d.ts.map +1 -0
- package/dist/jsonrpc/messages.js +23 -0
- package/dist/jsonrpc/messages.js.map +1 -0
- package/dist/jsonrpc/reader.d.ts +44 -0
- package/dist/jsonrpc/reader.d.ts.map +1 -0
- package/dist/jsonrpc/reader.js +107 -0
- package/dist/jsonrpc/reader.js.map +1 -0
- package/dist/jsonrpc/schemas.d.ts +103 -0
- package/dist/jsonrpc/schemas.d.ts.map +1 -0
- package/dist/jsonrpc/schemas.js +66 -0
- package/dist/jsonrpc/schemas.js.map +1 -0
- package/dist/jsonrpc/writer.d.ts +43 -0
- package/dist/jsonrpc/writer.d.ts.map +1 -0
- package/dist/jsonrpc/writer.js +110 -0
- package/dist/jsonrpc/writer.js.map +1 -0
- package/dist/middleware/compose.d.ts +3 -0
- package/dist/middleware/compose.d.ts.map +1 -0
- package/dist/middleware/compose.js +16 -0
- package/dist/middleware/compose.js.map +1 -0
- package/dist/middleware/index.d.ts +6 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +5 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/pipeline.d.ts +5 -0
- package/dist/middleware/pipeline.d.ts.map +1 -0
- package/dist/middleware/pipeline.js +54 -0
- package/dist/middleware/pipeline.js.map +1 -0
- package/dist/middleware/scoped.d.ts +9 -0
- package/dist/middleware/scoped.d.ts.map +1 -0
- package/dist/middleware/scoped.js +20 -0
- package/dist/middleware/scoped.js.map +1 -0
- package/dist/middleware/typed.d.ts +3 -0
- package/dist/middleware/typed.d.ts.map +1 -0
- package/dist/middleware/typed.js +18 -0
- package/dist/middleware/typed.js.map +1 -0
- package/dist/middleware/types.d.ts +45 -0
- package/dist/middleware/types.d.ts.map +1 -0
- package/dist/middleware/types.js +2 -0
- package/dist/middleware/types.js.map +1 -0
- package/dist/protocol/capabilities.d.ts +93 -0
- package/dist/protocol/capabilities.d.ts.map +1 -0
- package/dist/protocol/capabilities.js +143 -0
- package/dist/protocol/capabilities.js.map +1 -0
- package/dist/protocol/capability-methods.d.ts +270 -0
- package/dist/protocol/capability-methods.d.ts.map +1 -0
- package/dist/protocol/capability-methods.js +14 -0
- package/dist/protocol/capability-methods.js.map +1 -0
- package/dist/protocol/enums.d.ts +336 -0
- package/dist/protocol/enums.d.ts.map +1 -0
- package/dist/protocol/enums.js +417 -0
- package/dist/protocol/enums.js.map +1 -0
- package/dist/protocol/infer.d.ts +112 -0
- package/dist/protocol/infer.d.ts.map +1 -0
- package/dist/protocol/infer.js +58 -0
- package/dist/protocol/infer.js.map +1 -0
- package/dist/protocol/namespaces.d.ts +1432 -0
- package/dist/protocol/namespaces.d.ts.map +1 -0
- package/dist/protocol/namespaces.js +570 -0
- package/dist/protocol/namespaces.js.map +1 -0
- package/dist/protocol/partial.d.ts +29 -0
- package/dist/protocol/partial.d.ts.map +1 -0
- package/dist/protocol/partial.js +24 -0
- package/dist/protocol/partial.js.map +1 -0
- package/dist/protocol/progress.d.ts +32 -0
- package/dist/protocol/progress.d.ts.map +1 -0
- package/dist/protocol/progress.js +60 -0
- package/dist/protocol/progress.js.map +1 -0
- package/dist/protocol/schemas.d.ts +534 -0
- package/dist/protocol/schemas.d.ts.map +1 -0
- package/dist/protocol/schemas.js +271 -0
- package/dist/protocol/schemas.js.map +1 -0
- package/dist/protocol/types.d.ts +19 -0
- package/dist/protocol/types.d.ts.map +1 -0
- package/dist/protocol/types.js +8 -0
- package/dist/protocol/types.js.map +1 -0
- package/dist/protocol/watching.d.ts +29 -0
- package/dist/protocol/watching.d.ts.map +1 -0
- package/dist/protocol/watching.js +36 -0
- package/dist/protocol/watching.js.map +1 -0
- package/dist/protocol/workspace.d.ts +24 -0
- package/dist/protocol/workspace.d.ts.map +1 -0
- package/dist/protocol/workspace.js +26 -0
- package/dist/protocol/workspace.js.map +1 -0
- package/dist/transport/events.d.ts +27 -0
- package/dist/transport/events.d.ts.map +1 -0
- package/dist/transport/events.js +34 -0
- package/dist/transport/events.js.map +1 -0
- package/dist/transport/stdio.d.ts +55 -0
- package/dist/transport/stdio.d.ts.map +1 -0
- package/dist/transport/stdio.js +147 -0
- package/dist/transport/stdio.js.map +1 -0
- package/dist/transport/transport.d.ts +38 -0
- package/dist/transport/transport.d.ts.map +1 -0
- package/dist/transport/transport.js +5 -0
- package/dist/transport/transport.js.map +1 -0
- package/dist/transport/websocket.d.ts +142 -0
- package/dist/transport/websocket.d.ts.map +1 -0
- package/dist/transport/websocket.js +324 -0
- package/dist/transport/websocket.js.map +1 -0
- package/dist/utils/cancellation.d.ts +47 -0
- package/dist/utils/cancellation.d.ts.map +1 -0
- package/dist/utils/cancellation.js +77 -0
- package/dist/utils/cancellation.js.map +1 -0
- package/dist/utils/disposable-event-emitter.d.ts +26 -0
- package/dist/utils/disposable-event-emitter.d.ts.map +1 -0
- package/dist/utils/disposable-event-emitter.js +62 -0
- package/dist/utils/disposable-event-emitter.js.map +1 -0
- package/dist/utils/disposable.d.ts +34 -0
- package/dist/utils/disposable.d.ts.map +1 -0
- package/dist/utils/disposable.js +55 -0
- package/dist/utils/disposable.js.map +1 -0
- package/dist/utils/document.d.ts +20 -0
- package/dist/utils/document.d.ts.map +1 -0
- package/dist/utils/document.js +54 -0
- package/dist/utils/document.js.map +1 -0
- package/dist/utils/errors.d.ts +69 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +104 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/handler-registry.d.ts +35 -0
- package/dist/utils/handler-registry.d.ts.map +1 -0
- package/dist/utils/handler-registry.js +68 -0
- package/dist/utils/handler-registry.js.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/internal.d.ts +3 -0
- package/dist/utils/internal.d.ts.map +1 -0
- package/dist/utils/internal.js +3 -0
- package/dist/utils/internal.js.map +1 -0
- package/dist/utils/logger.d.ts +47 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +61 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/pending-request-tracker.d.ts +36 -0
- package/dist/utils/pending-request-tracker.d.ts.map +1 -0
- package/dist/utils/pending-request-tracker.js +93 -0
- package/dist/utils/pending-request-tracker.js.map +1 -0
- package/dist/utils/transport-attachment.d.ts +34 -0
- package/dist/utils/transport-attachment.d.ts.map +1 -0
- package/dist/utils/transport-attachment.js +48 -0
- package/dist/utils/transport-attachment.js.map +1 -0
- package/package.json +87 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StdioTransport - Transport implementation using stdin/stdout
|
|
3
|
+
*/
|
|
4
|
+
import { MessageReader } from '../jsonrpc/reader.js';
|
|
5
|
+
import { MessageWriter } from '../jsonrpc/writer.js';
|
|
6
|
+
/**
|
|
7
|
+
* Transport implementation using stdin/stdout streams
|
|
8
|
+
*/
|
|
9
|
+
export class StdioTransport {
|
|
10
|
+
reader;
|
|
11
|
+
writer;
|
|
12
|
+
connected;
|
|
13
|
+
messageHandlers;
|
|
14
|
+
errorHandlers;
|
|
15
|
+
closeHandlers;
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
const input = options.input ?? process.stdin;
|
|
18
|
+
const output = options.output ?? process.stdout;
|
|
19
|
+
this.reader = new MessageReader(input);
|
|
20
|
+
this.writer = new MessageWriter(output);
|
|
21
|
+
this.connected = true;
|
|
22
|
+
this.messageHandlers = new Set();
|
|
23
|
+
this.errorHandlers = new Set();
|
|
24
|
+
this.closeHandlers = new Set();
|
|
25
|
+
// Set up reader event handlers
|
|
26
|
+
this.reader.on('message', (message) => {
|
|
27
|
+
this.messageHandlers.forEach((handler) => {
|
|
28
|
+
try {
|
|
29
|
+
handler(message);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.error('Error in message handler:', error);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
this.reader.on('error', (error) => {
|
|
37
|
+
this.errorHandlers.forEach((handler) => {
|
|
38
|
+
try {
|
|
39
|
+
handler(error);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
console.error('Error in error handler:', err);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
this.reader.on('close', () => {
|
|
47
|
+
this.handleClose();
|
|
48
|
+
});
|
|
49
|
+
// Set up writer event handlers
|
|
50
|
+
this.writer.on('error', (error) => {
|
|
51
|
+
this.errorHandlers.forEach((handler) => {
|
|
52
|
+
try {
|
|
53
|
+
handler(error);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
console.error('Error in error handler:', err);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
this.writer.on('close', () => {
|
|
61
|
+
this.handleClose();
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Send a message
|
|
66
|
+
*/
|
|
67
|
+
async send(message) {
|
|
68
|
+
if (!this.connected) {
|
|
69
|
+
throw new Error('Transport is not connected');
|
|
70
|
+
}
|
|
71
|
+
await this.writer.write(message);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Subscribe to incoming messages
|
|
75
|
+
*/
|
|
76
|
+
onMessage(handler) {
|
|
77
|
+
this.messageHandlers.add(handler);
|
|
78
|
+
return {
|
|
79
|
+
dispose: () => {
|
|
80
|
+
this.messageHandlers.delete(handler);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Subscribe to errors
|
|
86
|
+
*/
|
|
87
|
+
onError(handler) {
|
|
88
|
+
this.errorHandlers.add(handler);
|
|
89
|
+
return {
|
|
90
|
+
dispose: () => {
|
|
91
|
+
this.errorHandlers.delete(handler);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Subscribe to close events
|
|
97
|
+
*/
|
|
98
|
+
onClose(handler) {
|
|
99
|
+
this.closeHandlers.add(handler);
|
|
100
|
+
return {
|
|
101
|
+
dispose: () => {
|
|
102
|
+
this.closeHandlers.delete(handler);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Close the transport
|
|
108
|
+
*/
|
|
109
|
+
async close() {
|
|
110
|
+
if (!this.connected) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.reader.close();
|
|
114
|
+
this.writer.close();
|
|
115
|
+
// Don't close stdin/stdout as they're shared resources
|
|
116
|
+
// Just clean up our handlers
|
|
117
|
+
this.handleClose();
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Check if transport is connected
|
|
121
|
+
*/
|
|
122
|
+
isConnected() {
|
|
123
|
+
return this.connected;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Handle connection close
|
|
127
|
+
*/
|
|
128
|
+
handleClose() {
|
|
129
|
+
if (!this.connected) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
this.connected = false;
|
|
133
|
+
this.closeHandlers.forEach((handler) => {
|
|
134
|
+
try {
|
|
135
|
+
handler();
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
console.error('Error in close handler:', error);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
// Clear all handlers
|
|
142
|
+
this.messageHandlers.clear();
|
|
143
|
+
this.errorHandlers.clear();
|
|
144
|
+
this.closeHandlers.clear();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=stdio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/transport/stdio.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport interface for pluggable communication layers
|
|
3
|
+
*/
|
|
4
|
+
import type { Message } from '../jsonrpc/messages.js';
|
|
5
|
+
import type { Disposable } from '../utils/disposable.js';
|
|
6
|
+
/**
|
|
7
|
+
* Transport interface for sending/receiving JSON-RPC messages
|
|
8
|
+
*/
|
|
9
|
+
export interface Transport {
|
|
10
|
+
/**
|
|
11
|
+
* Send a message to the remote peer
|
|
12
|
+
*/
|
|
13
|
+
send(message: Message): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Subscribe to incoming messages
|
|
16
|
+
* @returns Disposable to unsubscribe
|
|
17
|
+
*/
|
|
18
|
+
onMessage(handler: (message: Message) => void): Disposable;
|
|
19
|
+
/**
|
|
20
|
+
* Subscribe to transport errors
|
|
21
|
+
* @returns Disposable to unsubscribe
|
|
22
|
+
*/
|
|
23
|
+
onError(handler: (error: Error) => void): Disposable;
|
|
24
|
+
/**
|
|
25
|
+
* Subscribe to connection close
|
|
26
|
+
* @returns Disposable to unsubscribe
|
|
27
|
+
*/
|
|
28
|
+
onClose(handler: () => void): Disposable;
|
|
29
|
+
/**
|
|
30
|
+
* Close the transport connection
|
|
31
|
+
*/
|
|
32
|
+
close(): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Check if transport is connected
|
|
35
|
+
*/
|
|
36
|
+
isConnected(): boolean;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/transport/transport.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtC;;;OAGG;IACH,SAAS,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,UAAU,CAAC;IAE3D;;;OAGG;IACH,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,UAAU,CAAC;IAErD;;;OAGG;IACH,OAAO,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,UAAU,CAAC;IAEzC;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB;;OAEG;IACH,WAAW,IAAI,OAAO,CAAC;CACxB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../../src/transport/transport.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Transport implementation for LSP
|
|
3
|
+
*/
|
|
4
|
+
import type { Transport } from './transport.js';
|
|
5
|
+
import type { Message } from '../jsonrpc/messages.js';
|
|
6
|
+
import type { Disposable } from '../utils/disposable.js';
|
|
7
|
+
type CloseLikeEvent = {
|
|
8
|
+
type: 'close';
|
|
9
|
+
};
|
|
10
|
+
type ErrorLikeEvent = {
|
|
11
|
+
error?: unknown;
|
|
12
|
+
};
|
|
13
|
+
type MessageLikeEvent = {
|
|
14
|
+
data: unknown;
|
|
15
|
+
};
|
|
16
|
+
type WebSocketLike = {
|
|
17
|
+
readonly readyState: number;
|
|
18
|
+
send(data: string, callback?: (error?: Error) => void): void;
|
|
19
|
+
close(): void;
|
|
20
|
+
on?(event: 'open', handler: () => void): void;
|
|
21
|
+
on?(event: 'message', handler: (data: Buffer | string) => void): void;
|
|
22
|
+
on?(event: 'error', handler: (error: Error) => void): void;
|
|
23
|
+
on?(event: 'close', handler: () => void): void;
|
|
24
|
+
once?(event: 'close', handler: () => void): void;
|
|
25
|
+
addEventListener?(event: 'open', handler: () => void): void;
|
|
26
|
+
addEventListener?(event: 'message', handler: (event: MessageLikeEvent) => void): void;
|
|
27
|
+
addEventListener?(event: 'error', handler: (event: ErrorLikeEvent) => void): void;
|
|
28
|
+
addEventListener?(event: 'close', handler: (event: CloseLikeEvent) => void): void;
|
|
29
|
+
};
|
|
30
|
+
export interface WebSocketTransportOptions {
|
|
31
|
+
/**
|
|
32
|
+
* WebSocket URL for client mode (e.g., 'ws://localhost:3000')
|
|
33
|
+
*/
|
|
34
|
+
url?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Existing WebSocket instance for server mode
|
|
37
|
+
*/
|
|
38
|
+
socket?: WebSocketLike;
|
|
39
|
+
/**
|
|
40
|
+
* Enable automatic reconnection (client mode only)
|
|
41
|
+
*/
|
|
42
|
+
enableReconnect?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Maximum number of reconnection attempts
|
|
45
|
+
* @default 5
|
|
46
|
+
*/
|
|
47
|
+
maxReconnectAttempts?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Initial delay between reconnection attempts in milliseconds
|
|
50
|
+
* @default 1000
|
|
51
|
+
*/
|
|
52
|
+
reconnectDelay?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Maximum delay between reconnection attempts in milliseconds
|
|
55
|
+
* @default 30000
|
|
56
|
+
*/
|
|
57
|
+
maxReconnectDelay?: number;
|
|
58
|
+
/**
|
|
59
|
+
* Multiplier for exponential backoff
|
|
60
|
+
* @default 2
|
|
61
|
+
*/
|
|
62
|
+
reconnectBackoffMultiplier?: number;
|
|
63
|
+
}
|
|
64
|
+
export declare function createWebSocketClient(url: string): WebSocketLike;
|
|
65
|
+
/**
|
|
66
|
+
* WebSocket-based transport for LSP communication
|
|
67
|
+
*
|
|
68
|
+
* Supports both client and server modes:
|
|
69
|
+
* - Client mode: Connects to a WebSocket server URL
|
|
70
|
+
* - Server mode: Wraps an existing WebSocket connection
|
|
71
|
+
*/
|
|
72
|
+
export declare class WebSocketTransport implements Transport {
|
|
73
|
+
private socket;
|
|
74
|
+
private connected;
|
|
75
|
+
private messageHandlers;
|
|
76
|
+
private errorHandlers;
|
|
77
|
+
private closeHandlers;
|
|
78
|
+
private readonly mode;
|
|
79
|
+
private readonly url;
|
|
80
|
+
private readonly enableReconnect;
|
|
81
|
+
private readonly maxReconnectAttempts;
|
|
82
|
+
private readonly reconnectDelay;
|
|
83
|
+
private readonly maxReconnectDelay;
|
|
84
|
+
private readonly reconnectBackoffMultiplier;
|
|
85
|
+
private reconnectAttempts;
|
|
86
|
+
private reconnectTimer;
|
|
87
|
+
private intentionallyClosed;
|
|
88
|
+
constructor(options: WebSocketTransportOptions);
|
|
89
|
+
/**
|
|
90
|
+
* Setup event handlers for the WebSocket
|
|
91
|
+
*/
|
|
92
|
+
private setupSocket;
|
|
93
|
+
private handleOpen;
|
|
94
|
+
private handleMessageData;
|
|
95
|
+
private handleClose;
|
|
96
|
+
/**
|
|
97
|
+
* Schedule a reconnection attempt with exponential backoff
|
|
98
|
+
*/
|
|
99
|
+
private scheduleReconnect;
|
|
100
|
+
/**
|
|
101
|
+
* Attempt to reconnect to the server
|
|
102
|
+
*/
|
|
103
|
+
private attemptReconnect;
|
|
104
|
+
/**
|
|
105
|
+
* Notify all error handlers
|
|
106
|
+
*/
|
|
107
|
+
private notifyError;
|
|
108
|
+
/**
|
|
109
|
+
* Send a message through the WebSocket
|
|
110
|
+
*/
|
|
111
|
+
send(message: Message): Promise<void>;
|
|
112
|
+
/**
|
|
113
|
+
* Register a handler for incoming messages
|
|
114
|
+
*/
|
|
115
|
+
onMessage(handler: (message: Message) => void): Disposable;
|
|
116
|
+
/**
|
|
117
|
+
* Register a handler for transport errors
|
|
118
|
+
*/
|
|
119
|
+
onError(handler: (error: Error) => void): Disposable;
|
|
120
|
+
/**
|
|
121
|
+
* Register a handler for transport closure
|
|
122
|
+
*/
|
|
123
|
+
onClose(handler: () => void): Disposable;
|
|
124
|
+
/**
|
|
125
|
+
* Close the WebSocket connection
|
|
126
|
+
*/
|
|
127
|
+
close(): Promise<void>;
|
|
128
|
+
/**
|
|
129
|
+
* Check if the transport is currently connected
|
|
130
|
+
*/
|
|
131
|
+
isConnected(): boolean;
|
|
132
|
+
/**
|
|
133
|
+
* Get the current reconnection attempt count
|
|
134
|
+
*/
|
|
135
|
+
getReconnectAttempts(): number;
|
|
136
|
+
/**
|
|
137
|
+
* Check if reconnection is enabled
|
|
138
|
+
*/
|
|
139
|
+
isReconnectEnabled(): boolean;
|
|
140
|
+
}
|
|
141
|
+
export {};
|
|
142
|
+
//# sourceMappingURL=websocket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../../src/transport/websocket.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Transport implementation for LSP
|
|
3
|
+
*/
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
const READY_STATE_CONNECTING = 0;
|
|
6
|
+
const READY_STATE_OPEN = 1;
|
|
7
|
+
function hasEventEmitterAPI(socket) {
|
|
8
|
+
return typeof socket.on === 'function';
|
|
9
|
+
}
|
|
10
|
+
function getNativeWebSocketCtor() {
|
|
11
|
+
if (typeof globalThis.WebSocket === 'undefined') {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
const ctor = globalThis.WebSocket;
|
|
15
|
+
return (url) => new ctor(url);
|
|
16
|
+
}
|
|
17
|
+
function getWsWebSocketCtor() {
|
|
18
|
+
try {
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
const moduleValue = require('ws');
|
|
21
|
+
if (typeof moduleValue === 'function') {
|
|
22
|
+
return (url) => new moduleValue(url);
|
|
23
|
+
}
|
|
24
|
+
if (moduleValue.WebSocket) {
|
|
25
|
+
return (url) => new moduleValue.WebSocket(url);
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function createWebSocketClient(url) {
|
|
34
|
+
const nativeCtor = getNativeWebSocketCtor();
|
|
35
|
+
if (nativeCtor) {
|
|
36
|
+
return nativeCtor(url);
|
|
37
|
+
}
|
|
38
|
+
const wsCtor = getWsWebSocketCtor();
|
|
39
|
+
if (wsCtor) {
|
|
40
|
+
return wsCtor(url);
|
|
41
|
+
}
|
|
42
|
+
throw new Error('WebSocket client unavailable. Use Node.js >= 22.4 for native globalThis.WebSocket, or install "ws" as an optional peer dependency.');
|
|
43
|
+
}
|
|
44
|
+
function parseIncomingMessage(data) {
|
|
45
|
+
if (typeof data === 'string') {
|
|
46
|
+
return JSON.parse(data);
|
|
47
|
+
}
|
|
48
|
+
if (data instanceof Uint8Array) {
|
|
49
|
+
return JSON.parse(Buffer.from(data).toString('utf8'));
|
|
50
|
+
}
|
|
51
|
+
if (data instanceof ArrayBuffer) {
|
|
52
|
+
return JSON.parse(Buffer.from(data).toString('utf8'));
|
|
53
|
+
}
|
|
54
|
+
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(data)) {
|
|
55
|
+
return JSON.parse(data.toString('utf8'));
|
|
56
|
+
}
|
|
57
|
+
throw new Error('Unsupported WebSocket message payload type');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* WebSocket-based transport for LSP communication
|
|
61
|
+
*
|
|
62
|
+
* Supports both client and server modes:
|
|
63
|
+
* - Client mode: Connects to a WebSocket server URL
|
|
64
|
+
* - Server mode: Wraps an existing WebSocket connection
|
|
65
|
+
*/
|
|
66
|
+
export class WebSocketTransport {
|
|
67
|
+
socket;
|
|
68
|
+
connected = false;
|
|
69
|
+
messageHandlers = new Set();
|
|
70
|
+
errorHandlers = new Set();
|
|
71
|
+
closeHandlers = new Set();
|
|
72
|
+
// Reconnection state
|
|
73
|
+
mode;
|
|
74
|
+
url;
|
|
75
|
+
enableReconnect;
|
|
76
|
+
maxReconnectAttempts;
|
|
77
|
+
reconnectDelay;
|
|
78
|
+
maxReconnectDelay;
|
|
79
|
+
reconnectBackoffMultiplier;
|
|
80
|
+
reconnectAttempts = 0;
|
|
81
|
+
reconnectTimer;
|
|
82
|
+
intentionallyClosed = false;
|
|
83
|
+
constructor(options) {
|
|
84
|
+
if (!options.url && !options.socket) {
|
|
85
|
+
throw new Error('Either url or socket must be provided');
|
|
86
|
+
}
|
|
87
|
+
if (options.url && options.socket) {
|
|
88
|
+
throw new Error('Cannot provide both url and socket');
|
|
89
|
+
}
|
|
90
|
+
this.mode = options.socket ? 'server' : 'client';
|
|
91
|
+
this.url = options.url;
|
|
92
|
+
this.enableReconnect = options.enableReconnect ?? false;
|
|
93
|
+
this.maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
|
|
94
|
+
this.reconnectDelay = options.reconnectDelay ?? 1000;
|
|
95
|
+
this.maxReconnectDelay = options.maxReconnectDelay ?? 30000;
|
|
96
|
+
this.reconnectBackoffMultiplier = options.reconnectBackoffMultiplier ?? 2;
|
|
97
|
+
if (options.socket) {
|
|
98
|
+
this.socket = options.socket;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
this.socket = createWebSocketClient(this.url);
|
|
102
|
+
}
|
|
103
|
+
this.setupSocket();
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Setup event handlers for the WebSocket
|
|
107
|
+
*/
|
|
108
|
+
setupSocket() {
|
|
109
|
+
if (hasEventEmitterAPI(this.socket)) {
|
|
110
|
+
this.socket.on('open', () => this.handleOpen());
|
|
111
|
+
this.socket.on('message', (data) => this.handleMessageData(data));
|
|
112
|
+
this.socket.on('error', (error) => this.notifyError(error));
|
|
113
|
+
this.socket.on('close', () => this.handleClose());
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
this.socket.addEventListener?.('open', () => this.handleOpen());
|
|
117
|
+
this.socket.addEventListener?.('message', (event) => {
|
|
118
|
+
this.handleMessageData(event.data);
|
|
119
|
+
});
|
|
120
|
+
this.socket.addEventListener?.('error', (event) => {
|
|
121
|
+
const error = event.error instanceof Error ? event.error : new Error('WebSocket error');
|
|
122
|
+
this.notifyError(error);
|
|
123
|
+
});
|
|
124
|
+
this.socket.addEventListener?.('close', () => this.handleClose());
|
|
125
|
+
}
|
|
126
|
+
handleOpen() {
|
|
127
|
+
this.connected = true;
|
|
128
|
+
this.reconnectAttempts = 0;
|
|
129
|
+
if (this.reconnectTimer) {
|
|
130
|
+
clearTimeout(this.reconnectTimer);
|
|
131
|
+
this.reconnectTimer = undefined;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
handleMessageData(data) {
|
|
135
|
+
try {
|
|
136
|
+
const message = parseIncomingMessage(data);
|
|
137
|
+
for (const handler of this.messageHandlers) {
|
|
138
|
+
try {
|
|
139
|
+
handler(message);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Ignore message-handler failures to avoid breaking the transport loop.
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
148
|
+
this.notifyError(new Error(`Failed to parse message: ${err.message}`));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
handleClose() {
|
|
152
|
+
const wasConnected = this.connected;
|
|
153
|
+
this.connected = false;
|
|
154
|
+
for (const handler of this.closeHandlers) {
|
|
155
|
+
try {
|
|
156
|
+
handler();
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Ignore close-handler failures.
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (this.mode === 'client' &&
|
|
163
|
+
this.enableReconnect &&
|
|
164
|
+
!this.intentionallyClosed &&
|
|
165
|
+
wasConnected &&
|
|
166
|
+
this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
167
|
+
this.scheduleReconnect();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Schedule a reconnection attempt with exponential backoff
|
|
172
|
+
*/
|
|
173
|
+
scheduleReconnect() {
|
|
174
|
+
if (this.reconnectTimer) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
this.reconnectAttempts++;
|
|
178
|
+
const baseDelay = this.reconnectDelay * Math.pow(this.reconnectBackoffMultiplier, this.reconnectAttempts - 1);
|
|
179
|
+
const delay = Math.min(baseDelay, this.maxReconnectDelay);
|
|
180
|
+
this.reconnectTimer = setTimeout(() => {
|
|
181
|
+
this.reconnectTimer = undefined;
|
|
182
|
+
this.attemptReconnect();
|
|
183
|
+
}, delay);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Attempt to reconnect to the server
|
|
187
|
+
*/
|
|
188
|
+
attemptReconnect() {
|
|
189
|
+
if (this.intentionallyClosed || !this.url) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
this.socket = createWebSocketClient(this.url);
|
|
194
|
+
this.setupSocket();
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
198
|
+
this.notifyError(new Error(`Reconnection attempt ${this.reconnectAttempts} failed: ${err.message}`));
|
|
199
|
+
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
200
|
+
this.scheduleReconnect();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Notify all error handlers
|
|
206
|
+
*/
|
|
207
|
+
notifyError(error) {
|
|
208
|
+
for (const handler of this.errorHandlers) {
|
|
209
|
+
try {
|
|
210
|
+
handler(error);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// Ignore error-handler failures.
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Send a message through the WebSocket
|
|
219
|
+
*/
|
|
220
|
+
async send(message) {
|
|
221
|
+
if (!this.connected || this.socket.readyState !== READY_STATE_OPEN) {
|
|
222
|
+
throw new Error('WebSocket is not connected');
|
|
223
|
+
}
|
|
224
|
+
return new Promise((resolve, reject) => {
|
|
225
|
+
const data = JSON.stringify(message);
|
|
226
|
+
if (hasEventEmitterAPI(this.socket)) {
|
|
227
|
+
this.socket.send(data, (error) => {
|
|
228
|
+
if (error) {
|
|
229
|
+
reject(new Error(`Failed to send message: ${error.message}`));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
resolve();
|
|
233
|
+
});
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
this.socket.send(data);
|
|
238
|
+
resolve();
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
242
|
+
reject(new Error(`Failed to send message: ${err.message}`));
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Register a handler for incoming messages
|
|
248
|
+
*/
|
|
249
|
+
onMessage(handler) {
|
|
250
|
+
this.messageHandlers.add(handler);
|
|
251
|
+
return {
|
|
252
|
+
dispose: () => {
|
|
253
|
+
this.messageHandlers.delete(handler);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Register a handler for transport errors
|
|
259
|
+
*/
|
|
260
|
+
onError(handler) {
|
|
261
|
+
this.errorHandlers.add(handler);
|
|
262
|
+
return {
|
|
263
|
+
dispose: () => {
|
|
264
|
+
this.errorHandlers.delete(handler);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Register a handler for transport closure
|
|
270
|
+
*/
|
|
271
|
+
onClose(handler) {
|
|
272
|
+
this.closeHandlers.add(handler);
|
|
273
|
+
return {
|
|
274
|
+
dispose: () => {
|
|
275
|
+
this.closeHandlers.delete(handler);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Close the WebSocket connection
|
|
281
|
+
*/
|
|
282
|
+
async close() {
|
|
283
|
+
this.intentionallyClosed = true;
|
|
284
|
+
if (this.reconnectTimer) {
|
|
285
|
+
clearTimeout(this.reconnectTimer);
|
|
286
|
+
this.reconnectTimer = undefined;
|
|
287
|
+
}
|
|
288
|
+
if (this.socket.readyState === READY_STATE_OPEN ||
|
|
289
|
+
this.socket.readyState === READY_STATE_CONNECTING) {
|
|
290
|
+
return new Promise((resolve) => {
|
|
291
|
+
const cleanup = () => {
|
|
292
|
+
resolve();
|
|
293
|
+
};
|
|
294
|
+
if (hasEventEmitterAPI(this.socket) && this.socket.once) {
|
|
295
|
+
this.socket.once('close', cleanup);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
this.socket.addEventListener?.('close', cleanup);
|
|
299
|
+
}
|
|
300
|
+
setTimeout(cleanup, 5000);
|
|
301
|
+
this.socket.close();
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Check if the transport is currently connected
|
|
307
|
+
*/
|
|
308
|
+
isConnected() {
|
|
309
|
+
return this.connected && this.socket.readyState === READY_STATE_OPEN;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Get the current reconnection attempt count
|
|
313
|
+
*/
|
|
314
|
+
getReconnectAttempts() {
|
|
315
|
+
return this.reconnectAttempts;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Check if reconnection is enabled
|
|
319
|
+
*/
|
|
320
|
+
isReconnectEnabled() {
|
|
321
|
+
return this.enableReconnect;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
//# sourceMappingURL=websocket.js.map
|