websocket-text-relay 1.0.0
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/.eslintrc +29 -0
- package/LICENSE +21 -0
- package/README.md +40 -0
- package/docs/code-structure.md +77 -0
- package/docs/creating-text-editor-plugin.md +10 -0
- package/docs/dev-getting-started.md +8 -0
- package/index.js +87 -0
- package/package.json +45 -0
- package/src/language-server/JsonRpcInterface.js +126 -0
- package/src/language-server/JsonRpcInterface.test.js +496 -0
- package/src/language-server/LspReader.js +119 -0
- package/src/language-server/LspReader.test.js +508 -0
- package/src/language-server/LspWriter.js +44 -0
- package/src/language-server/LspWriter.test.js +179 -0
- package/src/language-server/constants.js +23 -0
- package/src/ui/css/fonts/Montserrat-Black.ttf +0 -0
- package/src/ui/css/fonts/Montserrat-Bold.ttf +0 -0
- package/src/ui/css/fonts/Montserrat-ExtraBold.ttf +0 -0
- package/src/ui/css/fonts/Montserrat-ExtraLight.ttf +0 -0
- package/src/ui/css/fonts/Montserrat-Light.ttf +0 -0
- package/src/ui/css/fonts/Montserrat-Medium.ttf +0 -0
- package/src/ui/css/fonts/Montserrat-Regular.ttf +0 -0
- package/src/ui/css/fonts/Montserrat-SemiBold.ttf +0 -0
- package/src/ui/css/fonts/Montserrat-Thin.ttf +0 -0
- package/src/ui/css/fonts.css +54 -0
- package/src/ui/css/main.css +198 -0
- package/src/ui/index.html +36 -0
- package/src/ui/js/components/ActivityTimeseriesGraph.js +149 -0
- package/src/ui/js/components/HeaderSummary.js +23 -0
- package/src/ui/js/components/ServerStatus.js +31 -0
- package/src/ui/js/components/SessionLabels.js +171 -0
- package/src/ui/js/components/SessionWedges.js +107 -0
- package/src/ui/js/components/StatusRing.js +42 -0
- package/src/ui/js/components/grids.js +29 -0
- package/src/ui/js/index.js +117 -0
- package/src/ui/js/main.js +97 -0
- package/src/ui/js/util/DependencyManager.js +31 -0
- package/src/ui/js/util/EventEmitter.js +40 -0
- package/src/ui/js/util/WebsocketClient.js +76 -0
- package/src/ui/js/util/constants.js +9 -0
- package/src/ui/js/util/drawing.js +128 -0
- package/src/websocket-interface/WebsocketClient.js +56 -0
- package/src/websocket-interface/WebsocketInterface.js +71 -0
- package/src/websocket-interface/WtrSession.js +122 -0
- package/src/websocket-interface/httpServer.js +58 -0
- package/src/websocket-interface/sessionManager.js +107 -0
- package/src/websocket-interface/util.js +12 -0
- package/src/websocket-interface/websocketApi.js +116 -0
- package/src/websocket-interface/websocketServer.js +18 -0
- package/start.js +4 -0
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
import { PassThrough } from 'node:stream'
|
|
2
|
+
import { describe, it, expect, beforeAll } from "vitest"
|
|
3
|
+
import { LspReader } from './LspReader.js'
|
|
4
|
+
import { parseErrorCode, parseHeaderErrorMessage, parseJsonErrorMessage } from './constants.js'
|
|
5
|
+
|
|
6
|
+
describe("LspReader", () => {
|
|
7
|
+
describe("simple case", () => {
|
|
8
|
+
describe("when a message comes through the input stream", () => {
|
|
9
|
+
const inputData = `Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}`
|
|
10
|
+
const expectedMessageObj = {"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}
|
|
11
|
+
let actualMessageObj
|
|
12
|
+
|
|
13
|
+
beforeAll(() => {
|
|
14
|
+
const inputStream = new PassThrough()
|
|
15
|
+
const lspReader = new LspReader()
|
|
16
|
+
inputStream.pipe(lspReader)
|
|
17
|
+
lspReader.on('data', (message) => {
|
|
18
|
+
actualMessageObj = message
|
|
19
|
+
})
|
|
20
|
+
inputStream.write(inputData)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it("should parse the incoming data and emit the json rpc message object", () => {
|
|
24
|
+
expect(actualMessageObj).to.deep.equal(expectedMessageObj)
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
describe("when content-length header isn't capitalized", () => {
|
|
29
|
+
const inputData = `content-length: 73\r\n\r\n{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}`
|
|
30
|
+
const expectedMessageObj = {"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}
|
|
31
|
+
let actualMessageObj
|
|
32
|
+
|
|
33
|
+
beforeAll(() => {
|
|
34
|
+
const inputStream = new PassThrough()
|
|
35
|
+
const lspReader = new LspReader()
|
|
36
|
+
inputStream.pipe(lspReader)
|
|
37
|
+
lspReader.on('data', (message) => {
|
|
38
|
+
actualMessageObj = message
|
|
39
|
+
})
|
|
40
|
+
inputStream.write(inputData)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it("should parse the incoming data and emit the json rpc message object", () => {
|
|
44
|
+
expect(actualMessageObj).to.deep.equal(expectedMessageObj)
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
describe("error cases", () => {
|
|
50
|
+
describe("when Content-Length header is missing", () => {
|
|
51
|
+
const inputData = `Bad-Header: 73\r\n\r\n{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}`
|
|
52
|
+
const expectedError = {code: parseErrorCode, message: parseHeaderErrorMessage}
|
|
53
|
+
let actualMessageObj = null
|
|
54
|
+
let actualError
|
|
55
|
+
|
|
56
|
+
beforeAll(() => {
|
|
57
|
+
const inputStream = new PassThrough()
|
|
58
|
+
const lspReader = new LspReader()
|
|
59
|
+
inputStream.pipe(lspReader)
|
|
60
|
+
lspReader.on('data', (message) => {
|
|
61
|
+
actualMessageObj = message
|
|
62
|
+
})
|
|
63
|
+
lspReader.on('parse-error', (error) => {
|
|
64
|
+
actualError = error
|
|
65
|
+
})
|
|
66
|
+
inputStream.write(inputData)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it("should emit an 'parse-error' event with the parse header error message", () => {
|
|
70
|
+
expect(actualError).to.deep.equal(expectedError)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it("should not emit any objects", () => {
|
|
74
|
+
expect(actualMessageObj).toBeNull()
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe("when recovering from a bad header error", () => {
|
|
79
|
+
const inputData = [
|
|
80
|
+
`Bad-Header: 73\r\n\r\n{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}`,
|
|
81
|
+
`Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"1","method":"test/test-method","params":{"b":"2"}}`
|
|
82
|
+
]
|
|
83
|
+
const expectedError = {code: parseErrorCode, message: parseHeaderErrorMessage}
|
|
84
|
+
const expectedObject = {"jsonrpc":"2.0","id":"1","method":"test/test-method","params":{"b":"2"}}
|
|
85
|
+
let actualMessageObjs = []
|
|
86
|
+
let actualErrors = []
|
|
87
|
+
|
|
88
|
+
beforeAll(() => {
|
|
89
|
+
const inputStream = new PassThrough()
|
|
90
|
+
const lspReader = new LspReader()
|
|
91
|
+
inputStream.pipe(lspReader)
|
|
92
|
+
lspReader.on('data', (message) => {
|
|
93
|
+
actualMessageObjs.push(message)
|
|
94
|
+
})
|
|
95
|
+
lspReader.on('parse-error', (error) => {
|
|
96
|
+
actualErrors.push(error)
|
|
97
|
+
})
|
|
98
|
+
inputData.forEach((data) => {
|
|
99
|
+
inputStream.write(data)
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it("should emit only 1 'parse-error' event with the parse header error message", () => {
|
|
104
|
+
expect(actualErrors.length).toEqual(1)
|
|
105
|
+
expect(actualErrors[0]).to.deep.equal(expectedError)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it("should not emit one object from the message with the valid header", () => {
|
|
109
|
+
expect(actualMessageObjs.length).toEqual(1)
|
|
110
|
+
expect(actualMessageObjs[0]).to.deep.equal(expectedObject)
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
describe("when recovering from an invalid JSON error", () => {
|
|
115
|
+
const inputData = [
|
|
116
|
+
`Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}>`,
|
|
117
|
+
`Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"1","method":"test/test-method","params":{"b":"2"}}`
|
|
118
|
+
]
|
|
119
|
+
const expectedError = {code: parseErrorCode, message: parseJsonErrorMessage}
|
|
120
|
+
const expectedObject = {"jsonrpc":"2.0","id":"1","method":"test/test-method","params":{"b":"2"}}
|
|
121
|
+
let actualMessageObjs = []
|
|
122
|
+
let actualErrors = []
|
|
123
|
+
|
|
124
|
+
beforeAll(() => {
|
|
125
|
+
const inputStream = new PassThrough()
|
|
126
|
+
const lspReader = new LspReader()
|
|
127
|
+
inputStream.pipe(lspReader)
|
|
128
|
+
lspReader.on('data', (message) => {
|
|
129
|
+
actualMessageObjs.push(message)
|
|
130
|
+
})
|
|
131
|
+
lspReader.on('parse-error', (error) => {
|
|
132
|
+
actualErrors.push(error)
|
|
133
|
+
})
|
|
134
|
+
inputData.forEach((data) => {
|
|
135
|
+
inputStream.write(data)
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it("should emit only 1 'parse-error' event with the parse header error message", () => {
|
|
140
|
+
expect(actualErrors.length).toEqual(1)
|
|
141
|
+
expect(actualErrors[0]).to.deep.equal(expectedError)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it("should not emit one object from the message with the valid header", () => {
|
|
145
|
+
expect(actualMessageObjs.length).toEqual(1)
|
|
146
|
+
expect(actualMessageObjs[0]).to.deep.equal(expectedObject)
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
describe("when JSON is invalid", () => {
|
|
151
|
+
const inputData = `Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}>`
|
|
152
|
+
const expectedError = {code: parseErrorCode, message: parseJsonErrorMessage}
|
|
153
|
+
let actualMessageObj = null
|
|
154
|
+
let actualError
|
|
155
|
+
|
|
156
|
+
beforeAll(() => {
|
|
157
|
+
const inputStream = new PassThrough()
|
|
158
|
+
const lspReader = new LspReader()
|
|
159
|
+
inputStream.pipe(lspReader)
|
|
160
|
+
lspReader.on('data', (message) => {
|
|
161
|
+
actualMessageObj = message
|
|
162
|
+
})
|
|
163
|
+
lspReader.on('parse-error', (error) => {
|
|
164
|
+
actualError = error
|
|
165
|
+
})
|
|
166
|
+
inputStream.write(inputData)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it("should emit an 'parse-error' event with the parse JSON error message", () => {
|
|
170
|
+
expect(actualError).to.deep.equal(expectedError)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it("should not emit any objects", () => {
|
|
174
|
+
expect(actualMessageObj).toBeNull()
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
describe("single message buffering cases", () => {
|
|
180
|
+
describe("when the message is split in the middle of the header", () => {
|
|
181
|
+
const inputData = [`Content-Len`, `gth: 73\r\n\r\n{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}`]
|
|
182
|
+
const expectedMessageObj = {"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}
|
|
183
|
+
let actualMessageObj
|
|
184
|
+
let actualError = null
|
|
185
|
+
|
|
186
|
+
beforeAll(() => {
|
|
187
|
+
const inputStream = new PassThrough()
|
|
188
|
+
const lspReader = new LspReader()
|
|
189
|
+
inputStream.pipe(lspReader)
|
|
190
|
+
lspReader.on('data', (message) => {
|
|
191
|
+
actualMessageObj = message
|
|
192
|
+
})
|
|
193
|
+
lspReader.on('parse-error', (error) => {
|
|
194
|
+
actualError = error
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
inputData.forEach((dataChunk) => {
|
|
198
|
+
inputStream.write(dataChunk)
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it("should not emit an error", () => {
|
|
203
|
+
expect(actualError).toBeNull()
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it("should buffer the data until it can parse the incoming data and emit the json rpc message object", () => {
|
|
207
|
+
expect(actualMessageObj).to.deep.equal(expectedMessageObj)
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
describe("when the message is split in the middle of the header many times", () => {
|
|
212
|
+
const inputData = [`Cont`, `ent-L`, `en`, `gth`, `: 7`, `3\r`, `\n`, `\r\n`, `{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}`]
|
|
213
|
+
const expectedMessageObj = {"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}
|
|
214
|
+
let actualMessageObj
|
|
215
|
+
let actualError = null
|
|
216
|
+
|
|
217
|
+
beforeAll(() => {
|
|
218
|
+
const inputStream = new PassThrough()
|
|
219
|
+
const lspReader = new LspReader()
|
|
220
|
+
inputStream.pipe(lspReader)
|
|
221
|
+
lspReader.on('data', (message) => {
|
|
222
|
+
actualMessageObj = message
|
|
223
|
+
})
|
|
224
|
+
lspReader.on('parse-error', (error) => {
|
|
225
|
+
actualError = error
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
inputData.forEach((dataChunk) => {
|
|
229
|
+
inputStream.write(dataChunk)
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
it("should not emit an error", () => {
|
|
234
|
+
expect(actualError).toBeNull()
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it("should buffer the data until it can parse the incoming data and emit the json rpc message object", () => {
|
|
238
|
+
expect(actualMessageObj).to.deep.equal(expectedMessageObj)
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
describe("when the message is split in the middle of the JSON", () => {
|
|
243
|
+
const inputData = [`Content-Length: 73\r\n\r\n{"jsonrpc":"2.0",`, `"id":"0","method":"test/test-method","params":{"a":"1"}}`]
|
|
244
|
+
const expectedMessageObj = {"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}
|
|
245
|
+
let actualMessageObj
|
|
246
|
+
let actualError = null
|
|
247
|
+
|
|
248
|
+
beforeAll(() => {
|
|
249
|
+
const inputStream = new PassThrough()
|
|
250
|
+
const lspReader = new LspReader()
|
|
251
|
+
inputStream.pipe(lspReader)
|
|
252
|
+
lspReader.on('data', (message) => {
|
|
253
|
+
actualMessageObj = message
|
|
254
|
+
})
|
|
255
|
+
lspReader.on('parse-error', (error) => {
|
|
256
|
+
actualError = error
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
inputData.forEach((dataChunk) => {
|
|
260
|
+
inputStream.write(dataChunk)
|
|
261
|
+
})
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it("should not emit an error", () => {
|
|
265
|
+
expect(actualError).toBeNull()
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
it("should buffer the data until it can parse the incoming data and emit the json rpc message object", () => {
|
|
269
|
+
expect(actualMessageObj).to.deep.equal(expectedMessageObj)
|
|
270
|
+
})
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
describe("when the message is split in the middle of the header and JSON many times", () => {
|
|
274
|
+
const inputData = [`Con`, `tent-Len`, `gth: 73\r`, `\n`, `\r\n`, `{`, `"jso`, `nrpc":"2.0"`, `,"id":"0","method":"te`, `st/test-method","params":{"a":`, `"1"}`, `}`]
|
|
275
|
+
const expectedMessageObj = {"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}
|
|
276
|
+
let actualMessageObj
|
|
277
|
+
let actualError = null
|
|
278
|
+
|
|
279
|
+
beforeAll(() => {
|
|
280
|
+
const inputStream = new PassThrough()
|
|
281
|
+
const lspReader = new LspReader()
|
|
282
|
+
inputStream.pipe(lspReader)
|
|
283
|
+
lspReader.on('data', (message) => {
|
|
284
|
+
actualMessageObj = message
|
|
285
|
+
})
|
|
286
|
+
lspReader.on('parse-error', (error) => {
|
|
287
|
+
actualError = error
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
inputData.forEach((dataChunk) => {
|
|
291
|
+
inputStream.write(dataChunk)
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
it("should not emit an error", () => {
|
|
296
|
+
expect(actualError).toBeNull()
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it("should buffer the data until it can parse the incoming data and emit the json rpc message object", () => {
|
|
300
|
+
expect(actualMessageObj).to.deep.equal(expectedMessageObj)
|
|
301
|
+
})
|
|
302
|
+
})
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
describe("when the buffer needs resizing", () => {
|
|
306
|
+
describe("when a message larger than the buffer size comes through the input stream", () => {
|
|
307
|
+
const inputData = `Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}`
|
|
308
|
+
const expectedMessageObj = {"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}
|
|
309
|
+
let lspReader
|
|
310
|
+
let actualMessageObj
|
|
311
|
+
let actualInitialBufferSize
|
|
312
|
+
|
|
313
|
+
beforeAll(() => {
|
|
314
|
+
const inputStream = new PassThrough()
|
|
315
|
+
lspReader = new LspReader({initialBufferSize: 10})
|
|
316
|
+
actualInitialBufferSize = lspReader.buffer.length
|
|
317
|
+
inputStream.pipe(lspReader)
|
|
318
|
+
lspReader.on('data', (message) => {
|
|
319
|
+
actualMessageObj = message
|
|
320
|
+
})
|
|
321
|
+
inputStream.write(inputData)
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
it("should construct the lspReader with the passed in initialBufferSize", () => {
|
|
325
|
+
expect(actualInitialBufferSize).toEqual(10)
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
it("should need to trigger a resize", () => {
|
|
329
|
+
expect(inputData.length).toBeGreaterThan(actualInitialBufferSize)
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
it("should increase the buffer to a size larger than the message chunk", () => {
|
|
333
|
+
expect(lspReader.buffer.length).toBeGreaterThan(inputData.length)
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
it("should parse the incoming data and emit the json rpc message object", () => {
|
|
337
|
+
expect(actualMessageObj).to.deep.equal(expectedMessageObj)
|
|
338
|
+
})
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
describe("when a buffer that already has data needs resizing", () => {
|
|
342
|
+
const inputData = [`Content-Len`, `gth: 73\r\n\r\n{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}`]
|
|
343
|
+
const expectedMessageObj = {"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}
|
|
344
|
+
let actualMessageObj
|
|
345
|
+
let lspReader
|
|
346
|
+
let actualInitialBufferSize
|
|
347
|
+
let actualError = null
|
|
348
|
+
|
|
349
|
+
beforeAll(() => {
|
|
350
|
+
const inputStream = new PassThrough()
|
|
351
|
+
lspReader = new LspReader({initialBufferSize: 70})
|
|
352
|
+
actualInitialBufferSize = lspReader.buffer.length
|
|
353
|
+
inputStream.pipe(lspReader)
|
|
354
|
+
lspReader.on('data', (message) => {
|
|
355
|
+
actualMessageObj = message
|
|
356
|
+
})
|
|
357
|
+
lspReader.on('parse-error', (error) => {
|
|
358
|
+
actualError = error
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
inputData.forEach((dataChunk) => {
|
|
362
|
+
inputStream.write(dataChunk)
|
|
363
|
+
})
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
it("should not emit an error", () => {
|
|
367
|
+
expect(actualError).toBeNull()
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it("should construct the lspReader with the passed in initialBufferSize", () => {
|
|
371
|
+
expect(actualInitialBufferSize).toEqual(70)
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
it("should need to trigger a resize after the first buffered message", () => {
|
|
375
|
+
expect(inputData[0].length).toBeLessThan(actualInitialBufferSize)
|
|
376
|
+
expect(inputData[1].length).toBeGreaterThan(actualInitialBufferSize)
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
it("should increase the buffer to a size larger than the combined message chunks", () => {
|
|
380
|
+
expect(lspReader.buffer.length).toBeGreaterThan(inputData[0].length + inputData[1].length)
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
it("should buffer the data until it can parse the incoming data and emit the json rpc message object", () => {
|
|
384
|
+
expect(actualMessageObj).to.deep.equal(expectedMessageObj)
|
|
385
|
+
})
|
|
386
|
+
})
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
describe("when multiple message come in separate chunks", () => {
|
|
390
|
+
const inputData = [
|
|
391
|
+
`Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}`,
|
|
392
|
+
`Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"1","method":"test/test-method","params":{"b":"2"}}`,
|
|
393
|
+
`Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"2","method":"test/test-method","params":{"c":"3"}}`
|
|
394
|
+
]
|
|
395
|
+
|
|
396
|
+
const expectedMessageObjs = [
|
|
397
|
+
{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}},
|
|
398
|
+
{"jsonrpc":"2.0","id":"1","method":"test/test-method","params":{"b":"2"}},
|
|
399
|
+
{"jsonrpc":"2.0","id":"2","method":"test/test-method","params":{"c":"3"}}
|
|
400
|
+
]
|
|
401
|
+
let actualMessageObjs = []
|
|
402
|
+
let actualError = null
|
|
403
|
+
|
|
404
|
+
beforeAll(() => {
|
|
405
|
+
const inputStream = new PassThrough()
|
|
406
|
+
const lspReader = new LspReader()
|
|
407
|
+
inputStream.pipe(lspReader)
|
|
408
|
+
lspReader.on('data', (message) => {
|
|
409
|
+
actualMessageObjs.push(message)
|
|
410
|
+
})
|
|
411
|
+
lspReader.on('parse-error', (error) => {
|
|
412
|
+
actualError = error
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
inputData.forEach((dataChunk) => {
|
|
416
|
+
inputStream.write(dataChunk)
|
|
417
|
+
})
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
it("should not emit an error", () => {
|
|
421
|
+
expect(actualError).toBeNull()
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
it("should emit each message in the order it was received", () => {
|
|
425
|
+
expect(actualMessageObjs).to.deep.equal(expectedMessageObjs)
|
|
426
|
+
})
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
describe("when multiple message come in the same chunk", () => {
|
|
430
|
+
const inputDataMessages = [
|
|
431
|
+
`Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}`,
|
|
432
|
+
`Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"1","method":"test/test-method","params":{"b":"2"}}`,
|
|
433
|
+
`Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"2","method":"test/test-method","params":{"c":"3"}}`
|
|
434
|
+
]
|
|
435
|
+
const inputData = inputDataMessages.join("")
|
|
436
|
+
|
|
437
|
+
const expectedMessageObjs = [
|
|
438
|
+
{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}},
|
|
439
|
+
{"jsonrpc":"2.0","id":"1","method":"test/test-method","params":{"b":"2"}},
|
|
440
|
+
{"jsonrpc":"2.0","id":"2","method":"test/test-method","params":{"c":"3"}}
|
|
441
|
+
]
|
|
442
|
+
let actualMessageObjs = []
|
|
443
|
+
let actualError = null
|
|
444
|
+
|
|
445
|
+
beforeAll(() => {
|
|
446
|
+
const inputStream = new PassThrough()
|
|
447
|
+
const lspReader = new LspReader()
|
|
448
|
+
inputStream.pipe(lspReader)
|
|
449
|
+
lspReader.on('data', (message) => {
|
|
450
|
+
actualMessageObjs.push(message)
|
|
451
|
+
})
|
|
452
|
+
lspReader.on('parse-error', (error) => {
|
|
453
|
+
actualError = error
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
inputStream.write(inputData)
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
it("should not emit an error", () => {
|
|
460
|
+
expect(actualError).toBeNull()
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
it("should emit each message in the order it was received", () => {
|
|
464
|
+
expect(actualMessageObjs).to.deep.equal(expectedMessageObjs)
|
|
465
|
+
})
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
describe("when buffering partial messages in a multi message chunk", () => {
|
|
469
|
+
describe("when the next message has an incomplete header", () => {
|
|
470
|
+
const inputData = [
|
|
471
|
+
`Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"1","method":"test/test-method","params":{"b":"2"}}Content-Lengt`,
|
|
472
|
+
`h: 73\r\n\r\n{"jsonrpc":"2.0","id":"2","method":"test/test-method","params":{"c":"3"}}`
|
|
473
|
+
]
|
|
474
|
+
|
|
475
|
+
const expectedMessageObjs = [
|
|
476
|
+
{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}},
|
|
477
|
+
{"jsonrpc":"2.0","id":"1","method":"test/test-method","params":{"b":"2"}},
|
|
478
|
+
{"jsonrpc":"2.0","id":"2","method":"test/test-method","params":{"c":"3"}}
|
|
479
|
+
]
|
|
480
|
+
let actualMessageObjs = []
|
|
481
|
+
let actualError = null
|
|
482
|
+
|
|
483
|
+
beforeAll(() => {
|
|
484
|
+
const inputStream = new PassThrough()
|
|
485
|
+
const lspReader = new LspReader()
|
|
486
|
+
inputStream.pipe(lspReader)
|
|
487
|
+
lspReader.on('data', (message) => {
|
|
488
|
+
actualMessageObjs.push(message)
|
|
489
|
+
})
|
|
490
|
+
lspReader.on('parse-error', (error) => {
|
|
491
|
+
actualError = error
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
inputData.forEach((dataChunk) => {
|
|
495
|
+
inputStream.write(dataChunk)
|
|
496
|
+
})
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
it("should not emit an error", () => {
|
|
500
|
+
expect(actualError).toBeNull()
|
|
501
|
+
})
|
|
502
|
+
|
|
503
|
+
it("should emit each message in the order it was received", () => {
|
|
504
|
+
expect(actualMessageObjs).to.deep.equal(expectedMessageObjs)
|
|
505
|
+
})
|
|
506
|
+
})
|
|
507
|
+
})
|
|
508
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { headerKey } from './constants.js'
|
|
2
|
+
|
|
3
|
+
const jsonrpc = "2.0"
|
|
4
|
+
|
|
5
|
+
// exported for testing purposes
|
|
6
|
+
export const createHeader = (messageStr) => {
|
|
7
|
+
const byteLength = Buffer.byteLength(messageStr)
|
|
8
|
+
return `${headerKey}: ${byteLength}\r\n\r\n`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// exported for testing purposes
|
|
12
|
+
export const writeToOutput = (outputStream, messageObj) => {
|
|
13
|
+
const outputStr = JSON.stringify(messageObj)
|
|
14
|
+
const header = createHeader(outputStr)
|
|
15
|
+
outputStream.write(header)
|
|
16
|
+
outputStream.write(outputStr)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const writeResponse = (outputStream, id, error, result) => {
|
|
20
|
+
const response = {jsonrpc, id}
|
|
21
|
+
if (error) {
|
|
22
|
+
response.error = error
|
|
23
|
+
} else {
|
|
24
|
+
response.result = result
|
|
25
|
+
}
|
|
26
|
+
writeToOutput(outputStream, response)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const writeNotification = (outputStream, method, params) => {
|
|
30
|
+
const message = {jsonrpc, method, params}
|
|
31
|
+
writeToOutput(outputStream, message)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let requestId = 0
|
|
35
|
+
const nextRequestId = () => String(requestId++)
|
|
36
|
+
// only to be used for testing!
|
|
37
|
+
export const TESTONLY_resetRequestId = () => requestId = 0
|
|
38
|
+
|
|
39
|
+
export const writeRequest = (outputStream, method, params) => {
|
|
40
|
+
const id = nextRequestId()
|
|
41
|
+
const request = {jsonrpc, id, method, params}
|
|
42
|
+
writeToOutput(outputStream, request)
|
|
43
|
+
return id
|
|
44
|
+
}
|