websocket-text-relay 1.1.6 → 1.1.8

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/README.md CHANGED
@@ -3,8 +3,6 @@
3
3
  This application connects to your text editor using the Language Server Protocol (LSP) and then starts a websocket
4
4
  server that front end clients can connect to and subscribe to file change events. This allows users to see their
5
5
  front end changes evaluated immediately as they type, without having to first save the file to disk or reload the browser.
6
- It's a similar concept to sites like CodePen and JsFiddle, except you can develop locally using your own text editor with all
7
- of your personalized plugins instead of having to use an in browser code editor.
8
6
 
9
7
  ## Usage
10
8
 
@@ -23,14 +21,14 @@ the status UI and verify everything is running by first starting up your text ed
23
21
 
24
22
  To use this on a professional level project that gives you the option to use modules, typescript, and react, I recommend using vite along with
25
23
  the plugin [vite-plugin-websocket-text-relay](https://github.com/niels4/vite-plugin-websocket-text-relay). This plugin gives you all the power of vite when developing while also hooking
26
- the live text updates into Vite's hot module reload system.
24
+ the live text updates into Vite's hot module reload system. See [live-demo-vite](https://github.com/niels4/live-demo-vite) for a complete project setup with Typescript, React, Vite, ViTest, and a simple router.
27
25
 
28
26
  If you want to use this as a learning tool to play around with UI concepts using simple projects involving 1 html, css, and javascript file,
29
- then check out this [simple reference project](https://github.com/niels4/wtr-simple-example). This is a great setup for following along with any short and focused web development tutorials.
27
+ then check out [live-demo-vanillajs](https://github.com/niels4/live-demo-vanillajs). This is a great setup for following along with any short and focused web development tutorials.
30
28
 
31
29
  And finally, the status UI for this project was also created using live updates from websocket-text-relay.
32
30
  In addition to giving you live feedback on the status and activity of the application, it is also meant to serve as a
33
- reference UI project that is more complicated than a single javascript file, but still doesn't use any external dependencies.
31
+ reference UI project that doesn't use any external dependencies.
34
32
 
35
33
  ## Developing
36
34
 
package/biome.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.4.8/schema.json",
3
+ "vcs": {
4
+ "enabled": true,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": true
7
+ },
8
+ "files": {
9
+ "ignoreUnknown": false,
10
+ "includes": ["src/**/*"]
11
+ },
12
+ "formatter": {
13
+ "enabled": true,
14
+ "indentStyle": "space",
15
+ "lineWidth": 110
16
+ },
17
+ "linter": {
18
+ "enabled": true,
19
+ "rules": {
20
+ "recommended": true
21
+ }
22
+ },
23
+ "javascript": {
24
+ "formatter": {
25
+ "quoteStyle": "double",
26
+ "semicolons": "asNeeded"
27
+ }
28
+ },
29
+ "assist": {
30
+ "enabled": true,
31
+ "actions": {
32
+ "source": {
33
+ "organizeImports": "on"
34
+ }
35
+ }
36
+ }
37
+ }
package/changelog.md CHANGED
@@ -1,3 +1,21 @@
1
+ ## 1.1.8 - 2026/03/22
2
+
3
+ ### Dependencies Update
4
+
5
+ - Switch to Biome for formatting and linting.
6
+ - Switch to native nodejs test runner
7
+ - Remove all old dependencies
8
+
9
+ ## 1.1.7 - 2025/12/19
10
+
11
+ ### UI Update
12
+ Allow default websocket port to be overridden with url search param: `?port=38378`
13
+
14
+ ## 1.1.6 - 2025/12/19
15
+
16
+ ### UI Update
17
+ Rewrite the UI to be simpler and fix browser compatibility issues.
18
+
1
19
  ## 1.1.5 - 2025/09/05
2
20
 
3
21
  Minor UI spacing fix
package/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "websocket-text-relay",
3
- "version": "1.1.6",
3
+ "version": "1.1.8",
4
4
  "description": "An LSP server for sending live file updates from your text editor to the front end via websockets.",
5
5
  "bin": {
6
6
  "websocket-text-relay": "./start.js"
7
7
  },
8
8
  "scripts": {
9
- "format": "prettier src --write",
10
- "lint": "eslint src",
11
- "lint:fix": "eslint src --fix",
12
- "test": "npm run lint && vitest run --coverage",
13
- "test:ui": "vitest --ui"
9
+ "format": "biome check --write",
10
+ "lint": "biome check",
11
+ "test:run": "node --test",
12
+ "test:coverage": "node --test --experimental-test-coverage",
13
+ "test:watch": "node --test --watch",
14
+ "test": "npm run lint && npm run test:run"
14
15
  },
15
16
  "type": "module",
16
17
  "main": "./src/index.js",
@@ -35,16 +36,10 @@
35
36
  "url": "https://github.com/niels4/websocket-text-relay/issues"
36
37
  },
37
38
  "homepage": "https://github.com/niels4/websocket-text-relay#readme",
38
- "devDependencies": {
39
- "@eslint/js": "9.39.2",
40
- "@vitest/coverage-v8": "4.0.16",
41
- "@vitest/ui": "4.0.16",
42
- "eslint": "9.39.2",
43
- "globals": "16.5.0",
44
- "prettier": "3.7.4",
45
- "vitest": "4.0.16"
46
- },
47
39
  "dependencies": {
40
+ "@biomejs/biome": "2.4.8",
41
+ "@types/node": "25.5.0",
42
+ "typescript": "5.9.3",
48
43
  "ws": "8.18.3"
49
44
  }
50
45
  }
package/src/index.js CHANGED
@@ -7,8 +7,8 @@ const resolvePort = (portParam) => {
7
7
  if (portParam != null) {
8
8
  return portParam
9
9
  }
10
- if (process.env["websocket_text_relay_port"] != null) {
11
- return process.env["websocket_text_relay_port"]
10
+ if (process.env.websocket_text_relay_port != null) {
11
+ return process.env.websocket_text_relay_port
12
12
  }
13
13
  return DEFAULT_WS_PORT
14
14
  }
@@ -1,6 +1,4 @@
1
1
  import { EventEmitter } from "node:events"
2
- import { LspReader } from "./LspReader.js"
3
- import { writeNotification, writeRequest, writeResponse } from "./LspWriter.js"
4
2
  import {
5
3
  invalidRequestErrorCode,
6
4
  invalidRequestErrorMessage,
@@ -11,6 +9,8 @@ import {
11
9
  unexpectedRequestErrorCode,
12
10
  unexpectedRequestErrorMessage,
13
11
  } from "./constants.js"
12
+ import { LspReader } from "./LspReader.js"
13
+ import { writeNotification, writeRequest, writeResponse } from "./LspWriter.js"
14
14
 
15
15
  export class JsonRpcInterface {
16
16
  constructor({ inputStream, outputStream }) {
@@ -34,7 +34,7 @@ export class JsonRpcInterface {
34
34
  onNotification(method, handler) {
35
35
  if (this.notificationHandlers.has(method)) {
36
36
  throw new Error(
37
- "Can only register one notification handler at a time. Duplicate method handlers for " + method,
37
+ `Can only register one notification handler at a time. Duplicate method handlers for ${method}`,
38
38
  )
39
39
  }
40
40
  this.notificationHandlers.set(method, handler)
@@ -47,7 +47,7 @@ export class JsonRpcInterface {
47
47
  onRequest(method, handler) {
48
48
  if (this.requestHandlers.has(method)) {
49
49
  throw new Error(
50
- "Can only register one request handler at a time. Duplicate method handlers for " + method,
50
+ `Can only register one request handler at a time. Duplicate method handlers for ${method}`,
51
51
  )
52
52
  }
53
53
  this.requestHandlers.set(method, handler)
@@ -1,7 +1,6 @@
1
- import { describe, it, expect, beforeAll } from "vitest"
1
+ import assert from "node:assert/strict"
2
2
  import { PassThrough, Writable } from "node:stream"
3
- import { JsonRpcInterface } from "./JsonRpcInterface.js"
4
- import { TESTONLY_resetRequestId } from "./LspWriter.js"
3
+ import { before as beforeAll, describe, it } from "node:test"
5
4
  import {
6
5
  invalidRequestErrorCode,
7
6
  invalidRequestErrorMessage,
@@ -12,6 +11,8 @@ import {
12
11
  unexpectedRequestErrorCode,
13
12
  unexpectedRequestErrorMessage,
14
13
  } from "./constants.js"
14
+ import { JsonRpcInterface } from "./JsonRpcInterface.js"
15
+ import { TESTONLY_resetRequestId } from "./LspWriter.js"
15
16
 
16
17
  describe("JsonRpcInterface", () => {
17
18
  describe("onNotification", () => {
@@ -34,14 +35,14 @@ describe("JsonRpcInterface", () => {
34
35
  })
35
36
 
36
37
  it("should call the handler with the correct params", () => {
37
- expect(actualParams).to.deep.equal(expectedParams)
38
+ assert.deepStrictEqual(actualParams, expectedParams)
38
39
  })
39
40
  })
40
41
 
41
42
  describe("when attempting to register duplicate onNotification handlers ", () => {
42
43
  let inputStream, outputStream, jsonRpc
43
- let f1 = () => {}
44
- let f2 = () => {}
44
+ const f1 = () => {}
45
+ const f2 = () => {}
45
46
 
46
47
  beforeAll(() => {
47
48
  inputStream = new PassThrough()
@@ -52,9 +53,7 @@ describe("JsonRpcInterface", () => {
52
53
  })
53
54
 
54
55
  it("should throw an error if we try to register the same method twice (without first removing the handler)", () => {
55
- expect(() => jsonRpc.onNotification("test/test-method", f2)).to.toThrowError(
56
- /duplicate method handlers/i,
57
- )
56
+ assert.throws(() => jsonRpc.onNotification("test/test-method", f2), /duplicate method handlers/i)
58
57
  })
59
58
  })
60
59
 
@@ -63,8 +62,8 @@ describe("JsonRpcInterface", () => {
63
62
  const incomingMessage = `Content-Length: 64\r\n\r\n{"jsonrpc":"2.0","method":"test/test-method","params":{"a":"1"}}`
64
63
  let f1Fired = false
65
64
  let f2Fired = false
66
- let f1 = () => (f1Fired = true)
67
- let f2 = () => (f2Fired = true)
65
+ const f1 = () => (f1Fired = true)
66
+ const f2 = () => (f2Fired = true)
68
67
 
69
68
  beforeAll(() => {
70
69
  inputStream = new PassThrough()
@@ -79,8 +78,8 @@ describe("JsonRpcInterface", () => {
79
78
  })
80
79
 
81
80
  it("should call function f2 instead of f1", () => {
82
- expect(f1Fired).to.equal(false)
83
- expect(f2Fired).to.equal(true)
81
+ assert.strictEqual(f1Fired, false)
82
+ assert.strictEqual(f2Fired, true)
84
83
  })
85
84
  })
86
85
 
@@ -111,7 +110,7 @@ describe("JsonRpcInterface", () => {
111
110
  })
112
111
 
113
112
  it("should fire the notification-error event with the correct error data", () => {
114
- expect(actualError).to.deep.equal(expectedError)
113
+ assert.deepStrictEqual(actualError, expectedError)
115
114
  })
116
115
  })
117
116
 
@@ -138,7 +137,7 @@ describe("JsonRpcInterface", () => {
138
137
  })
139
138
 
140
139
  it("should fire the notification-error event with the correct error data", () => {
141
- expect(actualError).to.deep.equal(expectedError)
140
+ assert.deepStrictEqual(actualError, expectedError)
142
141
  })
143
142
  })
144
143
  })
@@ -170,11 +169,11 @@ describe("JsonRpcInterface", () => {
170
169
  })
171
170
 
172
171
  it("should call the handler with the correct params", () => {
173
- expect(actualParams).to.deep.equal(expectedParams)
172
+ assert.deepStrictEqual(actualParams, expectedParams)
174
173
  })
175
174
 
176
175
  it("should write the response to the output stream", () => {
177
- expect(actualOutput).toEqual(expectedOutput)
176
+ assert.deepStrictEqual(actualOutput, expectedOutput)
178
177
  })
179
178
  })
180
179
 
@@ -207,23 +206,23 @@ describe("JsonRpcInterface", () => {
207
206
  })
208
207
 
209
208
  it("should call the handler with the correct params", () => {
210
- expect(actualParams).to.deep.equal(expectedParams)
209
+ assert.deepStrictEqual(actualParams, expectedParams)
211
210
  })
212
211
 
213
212
  it("should not write the response until the promise resolves", () => {
214
- expect(actualOutput).toEqual("")
213
+ assert.deepStrictEqual(actualOutput, "")
215
214
  })
216
215
 
217
216
  it("should write the response after the async function resolves with the result", async () => {
218
217
  await new Promise((resolve) => setTimeout(resolve, 1))
219
- expect(actualOutput).toEqual(expectedOutput)
218
+ assert.deepStrictEqual(actualOutput, expectedOutput)
220
219
  })
221
220
  })
222
221
 
223
222
  describe("when attempting to register duplicate onRequest handlers ", () => {
224
223
  let inputStream, outputStream, jsonRpc
225
- let f1 = () => {}
226
- let f2 = () => {}
224
+ const f1 = () => {}
225
+ const f2 = () => {}
227
226
 
228
227
  beforeAll(() => {
229
228
  inputStream = new PassThrough()
@@ -234,7 +233,7 @@ describe("JsonRpcInterface", () => {
234
233
  })
235
234
 
236
235
  it("should throw an error if we try to register the same method twice (without first removing the handler)", () => {
237
- expect(() => jsonRpc.onRequest("test/test-method", f2)).to.toThrowError(/duplicate method handlers/i)
236
+ assert.throws(() => jsonRpc.onRequest("test/test-method", f2), /duplicate method handlers/i)
238
237
  })
239
238
  })
240
239
 
@@ -242,8 +241,8 @@ describe("JsonRpcInterface", () => {
242
241
  const incomingMessage = `Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}`
243
242
  let f1Fired = false
244
243
  let f2Fired = false
245
- let f1 = () => (f1Fired = true)
246
- let f2 = () => (f2Fired = true)
244
+ const f1 = () => (f1Fired = true)
245
+ const f2 = () => (f2Fired = true)
247
246
 
248
247
  beforeAll(() => {
249
248
  const inputStream = new PassThrough()
@@ -258,8 +257,8 @@ describe("JsonRpcInterface", () => {
258
257
  })
259
258
 
260
259
  it("should call function f2 instead of f1", () => {
261
- expect(f1Fired).to.equal(false)
262
- expect(f2Fired).to.equal(true)
260
+ assert.strictEqual(f1Fired, false)
261
+ assert.strictEqual(f2Fired, true)
263
262
  })
264
263
  })
265
264
 
@@ -299,11 +298,11 @@ describe("JsonRpcInterface", () => {
299
298
  })
300
299
 
301
300
  it("should fire the notification-error event with the correct error data", () => {
302
- expect(actualError).to.deep.equal(expectedError)
301
+ assert.deepStrictEqual(actualError, expectedError)
303
302
  })
304
303
 
305
304
  it("should write the error to the output stream", () => {
306
- expect(actualOutput).toEqual(expectedOutput)
305
+ assert.deepStrictEqual(actualOutput, expectedOutput)
307
306
  })
308
307
  })
309
308
 
@@ -339,11 +338,11 @@ describe("JsonRpcInterface", () => {
339
338
  })
340
339
 
341
340
  it("should fire the notification-error event with the correct error data", () => {
342
- expect(actualError).to.deep.equal(expectedError)
341
+ assert.deepStrictEqual(actualError, expectedError)
343
342
  })
344
343
 
345
344
  it("should write the error to the output stream", () => {
346
- expect(actualOutput).toEqual(expectedOutput)
345
+ assert.deepStrictEqual(actualOutput, expectedOutput)
347
346
  })
348
347
  })
349
348
  })
@@ -367,7 +366,7 @@ describe("JsonRpcInterface", () => {
367
366
  })
368
367
 
369
368
  it("should write a notification message without an ID property on the output stream", () => {
370
- expect(actualOutput).toEqual(expectedOutput)
369
+ assert.deepStrictEqual(actualOutput, expectedOutput)
371
370
  })
372
371
  })
373
372
 
@@ -399,18 +398,18 @@ describe("JsonRpcInterface", () => {
399
398
  })
400
399
 
401
400
  it("should write the request message to the output stream", () => {
402
- expect(actualOutput).toEqual(expectedOutput)
401
+ assert.deepStrictEqual(actualOutput, expectedOutput)
403
402
  })
404
403
 
405
404
  it("should return an unresolved promise", () => {
406
- expect(requestPromise).toBeInstanceOf(Promise)
407
- expect(actualResult).toBeNull()
405
+ assert.strictEqual(requestPromise instanceof Promise, true)
406
+ assert.strictEqual(actualResult, null)
408
407
  })
409
408
 
410
409
  it("should resolve the promise when the client responds with a result", async () => {
411
410
  inputStream.write(clientResponse)
412
411
  const result = await requestPromise
413
- expect(result).to.deep.equal(expectedResult)
412
+ assert.deepStrictEqual(result, expectedResult)
414
413
  })
415
414
  })
416
415
 
@@ -441,18 +440,18 @@ describe("JsonRpcInterface", () => {
441
440
  })
442
441
 
443
442
  it("should write the request message to the output stream", () => {
444
- expect(actualOutput).toEqual(expectedOutput)
443
+ assert.deepStrictEqual(actualOutput, expectedOutput)
445
444
  })
446
445
 
447
446
  it("should return an unresolved promise", () => {
448
- expect(requestPromise).toBeInstanceOf(Promise)
449
- expect(actualResult).toBeNull()
447
+ assert.strictEqual(requestPromise instanceof Promise, true)
448
+ assert.strictEqual(actualResult, null)
450
449
  })
451
450
 
452
451
  it("should reject the promise when the client responds with an error", async () => {
453
452
  inputStream.write(clientResponse)
454
453
  await requestPromise.catch((error) => {
455
- expect(error).to.deep.equal(expectedError)
454
+ assert.deepStrictEqual(error, expectedError)
456
455
  })
457
456
  })
458
457
  })
@@ -476,7 +475,7 @@ describe("JsonRpcInterface", () => {
476
475
  })
477
476
 
478
477
  it("should write the error to the output stream", () => {
479
- expect(actualOutput).toEqual(expectedOutput)
478
+ assert.deepStrictEqual(actualOutput, expectedOutput)
480
479
  })
481
480
  })
482
481
 
@@ -516,11 +515,11 @@ describe("JsonRpcInterface", () => {
516
515
  })
517
516
 
518
517
  it("should emit an rpc-error event on the events emitter", () => {
519
- expect(actualError).to.deep.equal(expectedError)
518
+ assert.deepStrictEqual(actualError, expectedError)
520
519
  })
521
520
 
522
521
  it("should notify the client that it sent an invalid rpc message", () => {
523
- expect(actualOutput).toEqual(expectedOutput)
522
+ assert.deepStrictEqual(actualOutput, expectedOutput)
524
523
  })
525
524
  })
526
525
  })
@@ -23,7 +23,7 @@ const getJsonLength = (data, bufferStartIndex, jsonStartIndex) => {
23
23
  if (!match) {
24
24
  return null
25
25
  }
26
- return Number.parseInt(match[1])
26
+ return Number.parseInt(match[1], 10)
27
27
  }
28
28
 
29
29
  export class LspReader extends Transform {
@@ -1,7 +1,8 @@
1
+ import assert from "node:assert/strict"
1
2
  import { PassThrough } from "node:stream"
2
- import { describe, it, expect, beforeAll } from "vitest"
3
- import { LspReader } from "./LspReader.js"
3
+ import { before as beforeAll, describe, it } from "node:test"
4
4
  import { parseErrorCode, parseHeaderErrorMessage, parseJsonErrorMessage } from "./constants.js"
5
+ import { LspReader } from "./LspReader.js"
5
6
 
6
7
  describe("LspReader", () => {
7
8
  describe("simple case", () => {
@@ -26,7 +27,7 @@ describe("LspReader", () => {
26
27
  })
27
28
 
28
29
  it("should parse the incoming data and emit the json rpc message object", () => {
29
- expect(actualMessageObj).to.deep.equal(expectedMessageObj)
30
+ assert.deepStrictEqual(actualMessageObj, expectedMessageObj)
30
31
  })
31
32
  })
32
33
 
@@ -51,7 +52,7 @@ describe("LspReader", () => {
51
52
  })
52
53
 
53
54
  it("should parse the incoming data and emit the json rpc message object", () => {
54
- expect(actualMessageObj).to.deep.equal(expectedMessageObj)
55
+ assert.deepStrictEqual(actualMessageObj, expectedMessageObj)
55
56
  })
56
57
  })
57
58
  })
@@ -77,11 +78,11 @@ describe("LspReader", () => {
77
78
  })
78
79
 
79
80
  it("should emit an 'parse-error' event with the parse header error message", () => {
80
- expect(actualError).to.deep.equal(expectedError)
81
+ assert.deepStrictEqual(actualError, expectedError)
81
82
  })
82
83
 
83
84
  it("should not emit any objects", () => {
84
- expect(actualMessageObj).toBeNull()
85
+ assert.strictEqual(actualMessageObj, null)
85
86
  })
86
87
  })
87
88
 
@@ -97,8 +98,8 @@ describe("LspReader", () => {
97
98
  method: "test/test-method",
98
99
  params: { b: "2" },
99
100
  }
100
- let actualMessageObjs = []
101
- let actualErrors = []
101
+ const actualMessageObjs = []
102
+ const actualErrors = []
102
103
 
103
104
  beforeAll(() => {
104
105
  const inputStream = new PassThrough()
@@ -116,13 +117,13 @@ describe("LspReader", () => {
116
117
  })
117
118
 
118
119
  it("should emit only 1 'parse-error' event with the parse header error message", () => {
119
- expect(actualErrors.length).toEqual(1)
120
- expect(actualErrors[0]).to.deep.equal(expectedError)
120
+ assert.strictEqual(actualErrors.length, 1)
121
+ assert.deepStrictEqual(actualErrors[0], expectedError)
121
122
  })
122
123
 
123
124
  it("should not emit one object from the message with the valid header", () => {
124
- expect(actualMessageObjs.length).toEqual(1)
125
- expect(actualMessageObjs[0]).to.deep.equal(expectedObject)
125
+ assert.strictEqual(actualMessageObjs.length, 1)
126
+ assert.deepStrictEqual(actualMessageObjs[0], expectedObject)
126
127
  })
127
128
  })
128
129
 
@@ -138,8 +139,8 @@ describe("LspReader", () => {
138
139
  method: "test/test-method",
139
140
  params: { b: "2" },
140
141
  }
141
- let actualMessageObjs = []
142
- let actualErrors = []
142
+ const actualMessageObjs = []
143
+ const actualErrors = []
143
144
 
144
145
  beforeAll(() => {
145
146
  const inputStream = new PassThrough()
@@ -157,13 +158,13 @@ describe("LspReader", () => {
157
158
  })
158
159
 
159
160
  it("should emit only 1 'parse-error' event with the parse header error message", () => {
160
- expect(actualErrors.length).toEqual(1)
161
- expect(actualErrors[0]).to.deep.equal(expectedError)
161
+ assert.strictEqual(actualErrors.length, 1)
162
+ assert.deepStrictEqual(actualErrors[0], expectedError)
162
163
  })
163
164
 
164
165
  it("should not emit one object from the message with the valid header", () => {
165
- expect(actualMessageObjs.length).toEqual(1)
166
- expect(actualMessageObjs[0]).to.deep.equal(expectedObject)
166
+ assert.strictEqual(actualMessageObjs.length, 1)
167
+ assert.deepStrictEqual(actualMessageObjs[0], expectedObject)
167
168
  })
168
169
  })
169
170
 
@@ -187,11 +188,11 @@ describe("LspReader", () => {
187
188
  })
188
189
 
189
190
  it("should emit an 'parse-error' event with the parse JSON error message", () => {
190
- expect(actualError).to.deep.equal(expectedError)
191
+ assert.deepStrictEqual(actualError, expectedError)
191
192
  })
192
193
 
193
194
  it("should not emit any objects", () => {
194
- expect(actualMessageObj).toBeNull()
195
+ assert.strictEqual(actualMessageObj, null)
195
196
  })
196
197
  })
197
198
  })
@@ -228,11 +229,11 @@ describe("LspReader", () => {
228
229
  })
229
230
 
230
231
  it("should not emit an error", () => {
231
- expect(actualError).toBeNull()
232
+ assert.strictEqual(actualError, null)
232
233
  })
233
234
 
234
235
  it("should buffer the data until it can parse the incoming data and emit the json rpc message object", () => {
235
- expect(actualMessageObj).to.deep.equal(expectedMessageObj)
236
+ assert.deepStrictEqual(actualMessageObj, expectedMessageObj)
236
237
  })
237
238
  })
238
239
 
@@ -274,11 +275,11 @@ describe("LspReader", () => {
274
275
  })
275
276
 
276
277
  it("should not emit an error", () => {
277
- expect(actualError).toBeNull()
278
+ assert.strictEqual(actualError, null)
278
279
  })
279
280
 
280
281
  it("should buffer the data until it can parse the incoming data and emit the json rpc message object", () => {
281
- expect(actualMessageObj).to.deep.equal(expectedMessageObj)
282
+ assert.deepStrictEqual(actualMessageObj, expectedMessageObj)
282
283
  })
283
284
  })
284
285
 
@@ -313,11 +314,11 @@ describe("LspReader", () => {
313
314
  })
314
315
 
315
316
  it("should not emit an error", () => {
316
- expect(actualError).toBeNull()
317
+ assert.strictEqual(actualError, null)
317
318
  })
318
319
 
319
320
  it("should buffer the data until it can parse the incoming data and emit the json rpc message object", () => {
320
- expect(actualMessageObj).to.deep.equal(expectedMessageObj)
321
+ assert.deepStrictEqual(actualMessageObj, expectedMessageObj)
321
322
  })
322
323
  })
323
324
 
@@ -362,11 +363,11 @@ describe("LspReader", () => {
362
363
  })
363
364
 
364
365
  it("should not emit an error", () => {
365
- expect(actualError).toBeNull()
366
+ assert.strictEqual(actualError, null)
366
367
  })
367
368
 
368
369
  it("should buffer the data until it can parse the incoming data and emit the json rpc message object", () => {
369
- expect(actualMessageObj).to.deep.equal(expectedMessageObj)
370
+ assert.deepStrictEqual(actualMessageObj, expectedMessageObj)
370
371
  })
371
372
  })
372
373
  })
@@ -396,19 +397,19 @@ describe("LspReader", () => {
396
397
  })
397
398
 
398
399
  it("should construct the lspReader with the passed in initialBufferSize", () => {
399
- expect(actualInitialBufferSize).toEqual(10)
400
+ assert.strictEqual(actualInitialBufferSize, 10)
400
401
  })
401
402
 
402
403
  it("should need to trigger a resize", () => {
403
- expect(inputData.length).toBeGreaterThan(actualInitialBufferSize)
404
+ assert.strictEqual(inputData.length > actualInitialBufferSize, true)
404
405
  })
405
406
 
406
407
  it("should increase the buffer to a size larger than the message chunk", () => {
407
- expect(lspReader.buffer.length).toBeGreaterThan(inputData.length)
408
+ assert.strictEqual(lspReader.buffer.length > inputData.length, true)
408
409
  })
409
410
 
410
411
  it("should parse the incoming data and emit the json rpc message object", () => {
411
- expect(actualMessageObj).to.deep.equal(expectedMessageObj)
412
+ assert.deepStrictEqual(actualMessageObj, expectedMessageObj)
412
413
  })
413
414
  })
414
415
 
@@ -446,24 +447,24 @@ describe("LspReader", () => {
446
447
  })
447
448
 
448
449
  it("should not emit an error", () => {
449
- expect(actualError).toBeNull()
450
+ assert.strictEqual(actualError, null)
450
451
  })
451
452
 
452
453
  it("should construct the lspReader with the passed in initialBufferSize", () => {
453
- expect(actualInitialBufferSize).toEqual(70)
454
+ assert.strictEqual(actualInitialBufferSize, 70)
454
455
  })
455
456
 
456
457
  it("should need to trigger a resize after the first buffered message", () => {
457
- expect(inputData[0].length).toBeLessThan(actualInitialBufferSize)
458
- expect(inputData[1].length).toBeGreaterThan(actualInitialBufferSize)
458
+ assert.strictEqual(inputData[0].length < actualInitialBufferSize, true)
459
+ assert.strictEqual(inputData[1].length > actualInitialBufferSize, true)
459
460
  })
460
461
 
461
462
  it("should increase the buffer to a size larger than the combined message chunks", () => {
462
- expect(lspReader.buffer.length).toBeGreaterThan(inputData[0].length + inputData[1].length)
463
+ assert.strictEqual(lspReader.buffer.length > inputData[0].length + inputData[1].length, true)
463
464
  })
464
465
 
465
466
  it("should buffer the data until it can parse the incoming data and emit the json rpc message object", () => {
466
- expect(actualMessageObj).to.deep.equal(expectedMessageObj)
467
+ assert.deepStrictEqual(actualMessageObj, expectedMessageObj)
467
468
  })
468
469
  })
469
470
  })
@@ -480,7 +481,7 @@ describe("LspReader", () => {
480
481
  { jsonrpc: "2.0", id: "1", method: "test/test-method", params: { b: "2" } },
481
482
  { jsonrpc: "2.0", id: "2", method: "test/test-method", params: { c: "3" } },
482
483
  ]
483
- let actualMessageObjs = []
484
+ const actualMessageObjs = []
484
485
  let actualError = null
485
486
 
486
487
  beforeAll(() => {
@@ -500,11 +501,11 @@ describe("LspReader", () => {
500
501
  })
501
502
 
502
503
  it("should not emit an error", () => {
503
- expect(actualError).toBeNull()
504
+ assert.strictEqual(actualError, null)
504
505
  })
505
506
 
506
507
  it("should emit each message in the order it was received", () => {
507
- expect(actualMessageObjs).to.deep.equal(expectedMessageObjs)
508
+ assert.deepStrictEqual(actualMessageObjs, expectedMessageObjs)
508
509
  })
509
510
  })
510
511
 
@@ -521,7 +522,7 @@ describe("LspReader", () => {
521
522
  { jsonrpc: "2.0", id: "1", method: "test/test-method", params: { b: "2" } },
522
523
  { jsonrpc: "2.0", id: "2", method: "test/test-method", params: { c: "3" } },
523
524
  ]
524
- let actualMessageObjs = []
525
+ const actualMessageObjs = []
525
526
  let actualError = null
526
527
 
527
528
  beforeAll(() => {
@@ -539,11 +540,11 @@ describe("LspReader", () => {
539
540
  })
540
541
 
541
542
  it("should not emit an error", () => {
542
- expect(actualError).toBeNull()
543
+ assert.strictEqual(actualError, null)
543
544
  })
544
545
 
545
546
  it("should emit each message in the order it was received", () => {
546
- expect(actualMessageObjs).to.deep.equal(expectedMessageObjs)
547
+ assert.deepStrictEqual(actualMessageObjs, expectedMessageObjs)
547
548
  })
548
549
  })
549
550
 
@@ -559,7 +560,7 @@ describe("LspReader", () => {
559
560
  { jsonrpc: "2.0", id: "1", method: "test/test-method", params: { b: "2" } },
560
561
  { jsonrpc: "2.0", id: "2", method: "test/test-method", params: { c: "3" } },
561
562
  ]
562
- let actualMessageObjs = []
563
+ const actualMessageObjs = []
563
564
  let actualError = null
564
565
 
565
566
  beforeAll(() => {
@@ -579,11 +580,11 @@ describe("LspReader", () => {
579
580
  })
580
581
 
581
582
  it("should not emit an error", () => {
582
- expect(actualError).toBeNull()
583
+ assert.strictEqual(actualError, null)
583
584
  })
584
585
 
585
586
  it("should emit each message in the order it was received", () => {
586
- expect(actualMessageObjs).to.deep.equal(expectedMessageObjs)
587
+ assert.deepStrictEqual(actualMessageObjs, expectedMessageObjs)
587
588
  })
588
589
  })
589
590
  })
@@ -1,8 +1,9 @@
1
+ import assert from "node:assert/strict"
1
2
  import { Writable } from "node:stream"
2
- import { describe, it, expect, beforeAll } from "vitest"
3
+ import { before as beforeAll, describe, it } from "node:test"
3
4
  import {
4
- TESTONLY_resetRequestId,
5
5
  createHeader,
6
+ TESTONLY_resetRequestId,
6
7
  writeNotification,
7
8
  writeRequest,
8
9
  writeResponse,
@@ -17,7 +18,7 @@ describe("LspWriter", () => {
17
18
 
18
19
  it("Should create header with correct length", () => {
19
20
  const actual = createHeader(JSON.stringify(messageObj))
20
- expect(actual).toEqual(expectedHeader)
21
+ assert.deepStrictEqual(actual, expectedHeader)
21
22
  })
22
23
  })
23
24
 
@@ -27,7 +28,7 @@ describe("LspWriter", () => {
27
28
 
28
29
  it("Should create header with correct length", () => {
29
30
  const actual = createHeader(JSON.stringify(messageObj))
30
- expect(actual).toEqual(expectedHeader)
31
+ assert.deepStrictEqual(actual, expectedHeader)
31
32
  })
32
33
  })
33
34
 
@@ -37,7 +38,7 @@ describe("LspWriter", () => {
37
38
 
38
39
  it("Should create header with correct byte length, not string length", () => {
39
40
  const actual = createHeader(JSON.stringify(messageObj))
40
- expect(actual).toEqual(expectedHeader)
41
+ assert.deepStrictEqual(actual, expectedHeader)
41
42
  })
42
43
  })
43
44
  })
@@ -59,7 +60,7 @@ describe("LspWriter", () => {
59
60
  })
60
61
 
61
62
  it("should stringify the json and add a Content-Length header, then write the head and json to the output stream", () => {
62
- expect(actualOutput).toEqual(expectedOutput)
63
+ assert.deepEqual(actualOutput, expectedOutput)
63
64
  })
64
65
  })
65
66
  })
@@ -82,7 +83,7 @@ describe("LspWriter", () => {
82
83
  })
83
84
 
84
85
  it("should create a response object with a result property (and no error) and write it to the output stream", () => {
85
- expect(actualOutput).toEqual(expectedOutput)
86
+ assert.deepEqual(actualOutput, expectedOutput)
86
87
  })
87
88
  })
88
89
 
@@ -103,7 +104,7 @@ describe("LspWriter", () => {
103
104
  })
104
105
 
105
106
  it("should create a response object with an error property (and no result) and write it to the output stream", () => {
106
- expect(actualOutput).toEqual(expectedOutput)
107
+ assert.deepEqual(actualOutput, expectedOutput)
107
108
  })
108
109
  })
109
110
  })
@@ -126,7 +127,7 @@ describe("LspWriter", () => {
126
127
  })
127
128
 
128
129
  it("should write a notification message without an ID property on the output stream", () => {
129
- expect(actualOutput).toEqual(expectedOutput)
130
+ assert.deepEqual(actualOutput, expectedOutput)
130
131
  })
131
132
  })
132
133
  })
@@ -150,7 +151,7 @@ describe("LspWriter", () => {
150
151
  })
151
152
 
152
153
  it("should write a request message with an ID property on the output stream", () => {
153
- expect(actualOutput).toEqual(expectedOutput)
154
+ assert.deepEqual(actualOutput, expectedOutput)
154
155
  })
155
156
  })
156
157
 
@@ -179,7 +180,7 @@ describe("LspWriter", () => {
179
180
  })
180
181
 
181
182
  it("should write multiple request messages with increasing IDs", () => {
182
- expect(actualOutput).toEqual(expectedOutput)
183
+ assert.deepEqual(actualOutput, expectedOutput)
183
184
  })
184
185
  })
185
186
  })
package/src/ui/index.html CHANGED
@@ -1,41 +1,44 @@
1
1
  <!doctype html>
2
2
  <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
3
 
7
- <title>WTR Status</title>
4
+ <head>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
7
 
9
- <link rel="icon" type="image/png" href="favicon.png" />
10
- <link rel="stylesheet" href="css/fonts.css" />
11
- <style id="main_style"></style>
12
- <script type="module" src="js/setup.js" defer></script>
8
+ <title>WTR Status</title>
13
9
 
14
- <style>
15
- html {
16
- height: 100dvh;
17
- overscroll-behavior: none;
18
- }
10
+ <link rel="icon" type="image/png" href="favicon.png" />
11
+ <link rel="stylesheet" href="css/fonts.css" />
12
+ <style id="main_style"></style>
13
+ <script type="module" src="js/setup.js" defer></script>
19
14
 
20
- #svg_root {
21
- position: absolute;
22
- top: 0;
23
- left: 0;
24
- height: 100dvh;
25
- width: 100dvw;
26
- }
27
- </style>
28
- </head>
15
+ <style>
16
+ html {
17
+ height: 100dvh;
18
+ overscroll-behavior: none;
19
+ }
20
+
21
+ #svg_root {
22
+ position: absolute;
23
+ top: 0;
24
+ left: 0;
25
+ height: 100dvh;
26
+ width: 100dvw;
27
+ }
28
+ </style>
29
+ </head>
30
+
31
+ <body class="base_colors color_theme">
32
+ <svg id="svg_root" viewBox="-1 -1 2 2" xmlns="http://www.w3.org/2000/svg">
33
+ <title>Main Svg Canvas</title>
34
+ <g id="headers_group"></g>
35
+ <g id="editor_wedges_group" class="wedge_group"></g>
36
+ <g id="client_wedges_group" class="wedge_group"></g>
37
+ <g id="activity_time_series_group"></g>
38
+ <g id="activity_labels_group"></g>
39
+ <g id="status_ring_group"></g>
40
+ <g id="footer_status_group"></g>
41
+ </svg>
42
+ </body>
29
43
 
30
- <body class="base_colors color_theme">
31
- <svg id="svg_root" viewBox="-1 -1 2 2" xmlns="http://www.w3.org/2000/svg">
32
- <g id="headers_group"></g>
33
- <g id="editor_wedges_group" class="wedge_group"></g>
34
- <g id="client_wedges_group" class="wedge_group"></g>
35
- <g id="activity_time_series_group"></g>
36
- <g id="activity_labels_group"></g>
37
- <g id="status_ring_group"></g>
38
- <g id="footer_status_group"></g>
39
- </svg>
40
- </body>
41
44
  </html>
@@ -66,7 +66,7 @@ function processDataWindow(dataWindow) {
66
66
  }
67
67
  return `${i},${value}`
68
68
  })
69
- const path = "M " + coords.join(" L ")
69
+ const path = `M ${coords.join(" L ")}`
70
70
  return { path, maxValue }
71
71
  }
72
72
 
@@ -14,7 +14,7 @@ const currentStatus = {
14
14
  }
15
15
 
16
16
  const wtrStatusEmitter = new EventEmitter()
17
- wtrStatusEmitter.on = function (event, handler) {
17
+ wtrStatusEmitter.on = (event, handler) => {
18
18
  EventEmitter.prototype.on.call(wtrStatusEmitter, event, handler)
19
19
  // automatically push the data to the event handler when it initially subscribes
20
20
  if (event === "data") {
@@ -2,6 +2,7 @@ const allCleanupFunctions = new Map()
2
2
 
3
3
  export const eventSubscriber = (key) => {
4
4
  const previousCleanupFunctions = allCleanupFunctions.get(key)
5
+ // biome-ignore lint/suspicious/useIterableCallbackReturn: Keep the function a 1 liner
5
6
  previousCleanupFunctions?.forEach((f) => f())
6
7
 
7
8
  const cleanupFunctions = []
@@ -1,11 +1,13 @@
1
- import { WebsocketClient } from "./setup/WebsocketClient.js"
2
- import { getEvalOnChangeFiles, clearEvalOnChangeFiles } from "./setup/evalOnChange.js" // initialize dependencies on the global __WTR__ object
1
+ import { emitActivity } from "./data/wtrActivity.js"
3
2
  import { setIsOnline, setSessions } from "./data/wtrStatus.js"
3
+ import { clearEvalOnChangeFiles, getEvalOnChangeFiles } from "./setup/evalOnChange.js" // initialize dependencies on the global __WTR__ object
4
4
  import { eventSubscriber } from "./setup/eventSubscriber.js" // make sure the eventSubscriber function is available on the __WTR__ object
5
- import { emitActivity } from "./data/wtrActivity.js"
5
+ import { WebsocketClient } from "./setup/WebsocketClient.js"
6
+
7
+ const searchParams = new URLSearchParams(window.location.search)
6
8
 
7
9
  const FILE_PREFIX = "websocket-text-relay/src/ui/"
8
- const WS_PORT = 38378
10
+ const wsPort = searchParams.get("port") ?? 38378
9
11
  const { hostname, protocol } = window.location
10
12
  const wsProtocol = protocol === "http:" ? "ws" : "wss"
11
13
  const CSS_FILE = "css/main.css"
@@ -25,7 +27,7 @@ const jsFiles = [
25
27
  "js/components/activityLabels.js",
26
28
  ]
27
29
 
28
- const ws = new WebsocketClient({ port: WS_PORT, host: hostname, protocol: wsProtocol })
30
+ const ws = new WebsocketClient({ port: wsPort, host: hostname, protocol: wsProtocol })
29
31
 
30
32
  const cssElement = document.getElementById("main_style")
31
33
 
@@ -37,6 +39,7 @@ const handleJs = (contents, jsEndsWith) => {
37
39
  // register and clean up event handlers on a per file basis
38
40
  window.__WTR__.onEvent = eventSubscriber(jsEndsWith)
39
41
  try {
42
+ // biome-ignore lint/security/noGlobalEval: Live updates requires eval
40
43
  eval(contents)
41
44
  } catch (e) {
42
45
  window._lastEvalError = e
@@ -1,8 +1,8 @@
1
+ import { EventEmitter } from "node:events"
1
2
  import { WebsocketClient } from "./WebsocketClient.js"
2
3
  import { WtrSession } from "./WtrSession.js"
3
4
  import { apiMethods } from "./websocketApi.js"
4
5
  import { createWebsocketServer } from "./websocketServer.js"
5
- import { EventEmitter } from "node:events"
6
6
 
7
7
  const watchActiveFilesMessage = { method: "watch-editor-active-files" }
8
8
 
@@ -81,7 +81,7 @@ export class WebsocketInterface {
81
81
  _sendMessageToServer(message) {
82
82
  if (this.serverSession) {
83
83
  this.serverSession._handleApiMessage(message)
84
- } else if (this.wsClient && this.wsClient.socketOpen) {
84
+ } else if (this.wsClient?.socketOpen) {
85
85
  this.wsClient.sendMessage(message)
86
86
  }
87
87
  }
@@ -1,11 +1,11 @@
1
1
  import { EventEmitter } from "node:events"
2
- import { getNextId } from "./util.js"
3
2
  import {
4
- startSessionStatus,
5
3
  endSessionStatus,
6
- statusEvents,
7
4
  removeWatchedFileLinks,
5
+ startSessionStatus,
6
+ statusEvents,
8
7
  } from "./sessionManager.js"
8
+ import { getNextId } from "./util.js"
9
9
 
10
10
  export class WtrSession {
11
11
  constructor({ apiMethods, wsConnection, wsInterfaceEmitter }) {
@@ -118,7 +118,7 @@ export class WtrSession {
118
118
  }
119
119
 
120
120
  _handleApiMessage(message) {
121
- const method = message && message.method
121
+ const method = message?.method
122
122
  const methodHandler = this.apiMethods.get(method)
123
123
  if (!methodHandler) {
124
124
  this.emitter.emit("log", { level: "error", text: `unknown ws api method: ${method}` })
@@ -1,9 +1,10 @@
1
- import { createServer } from "node:http"
2
- import path from "node:path"
3
1
  import { createReadStream } from "node:fs"
4
2
  import fs from "node:fs/promises"
3
+ import { createServer } from "node:http"
4
+ import path from "node:path"
5
5
  import * as url from "node:url"
6
6
  import { isValidOrigin } from "./util.js"
7
+
7
8
  const parentDir = url.fileURLToPath(new URL("..", import.meta.url))
8
9
 
9
10
  const uiDirName = "ui"
@@ -19,7 +20,7 @@ const allowedFileTypes = new Map([
19
20
  ])
20
21
 
21
22
  const getFilePath = (url) => {
22
- url = url === "/" ? "./index.html" : "." + url
23
+ url = url === "/" ? "./index.html" : `.${url}`
23
24
  return path.join(uiDirPath, url)
24
25
  }
25
26
 
@@ -1,10 +1,10 @@
1
1
  import {
2
- triggerStatusUpdate,
3
- statusEvents,
2
+ addOpenedFileLinks,
4
3
  addWatchedFileLinks,
5
- removeWatchedFileLinks,
6
4
  removeOpenedFileLinks,
7
- addOpenedFileLinks,
5
+ removeWatchedFileLinks,
6
+ statusEvents,
7
+ triggerStatusUpdate,
8
8
  } from "./sessionManager.js"
9
9
 
10
10
  export const apiMethods = new Map([
@@ -1,8 +1,8 @@
1
1
  import { WebSocketServer } from "ws"
2
2
  import { createHttpServer } from "./httpServer.js"
3
- import { apiMethods } from "./websocketApi.js"
4
- import { WtrSession } from "./WtrSession.js"
5
3
  import { isValidOrigin } from "./util.js"
4
+ import { WtrSession } from "./WtrSession.js"
5
+ import { apiMethods } from "./websocketApi.js"
6
6
 
7
7
  export const createWebsocketServer = async ({ port, allowedHosts, allowNetworkAccess }) => {
8
8
  const httpServer = await createHttpServer({ port, allowedHosts, allowNetworkAccess }) // promise will reject if can't start HTTP server on specified port
package/.prettierignore DELETED
@@ -1,3 +0,0 @@
1
- # Ignore artifacts:
2
- build
3
- coverage
package/.prettierrc DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "semi": false,
3
- "printWidth": 110
4
- }
package/eslint.config.js DELETED
@@ -1,32 +0,0 @@
1
- import js from "@eslint/js"
2
- import globals from "globals"
3
- import { defineConfig } from "eslint/config"
4
-
5
- const wtrGlobals = {
6
- __WTR__: "readonly",
7
- }
8
-
9
- export default defineConfig([
10
- {
11
- files: ["**/*.{js,mjs,cjs}"],
12
- plugins: { js },
13
- extends: ["js/recommended"],
14
- languageOptions: { globals: { ...globals.node, ...globals.browser, ...wtrGlobals } },
15
- },
16
- {
17
- rules: {
18
- semi: ["error", "never"],
19
- indent: ["error", 2],
20
- "no-multiple-empty-lines": ["error", { max: 1 }],
21
- "no-unused-vars": 1,
22
- "no-return-assign": 0,
23
- "multiline-ternary": 0,
24
- "object-curly-spacing": 0,
25
- "object-property-newline": 0,
26
- "object-curly-newline": 0,
27
- quotes: 0,
28
- "quote-props": 0,
29
- "import/no-absolute-path": 0,
30
- },
31
- },
32
- ])