websocket-text-relay 1.1.2 → 1.1.4

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.
Files changed (40) hide show
  1. package/.prettierignore +3 -0
  2. package/.prettierrc +4 -0
  3. package/changelog.md +11 -0
  4. package/docs/code-structure.md +1 -1
  5. package/eslint.config.js +28 -0
  6. package/package.json +15 -10
  7. package/{index.js → src/index.js} +25 -23
  8. package/src/language-server/JsonRpcInterface.js +59 -29
  9. package/src/language-server/JsonRpcInterface.test.js +102 -72
  10. package/src/language-server/LspReader.js +30 -20
  11. package/src/language-server/LspReader.test.js +147 -65
  12. package/src/language-server/LspWriter.js +5 -5
  13. package/src/language-server/LspWriter.test.js +31 -24
  14. package/src/ui/css/fonts.css +0 -1
  15. package/src/ui/css/main.css +7 -7
  16. package/src/ui/index.html +4 -4
  17. package/src/ui/js/components/ActivityTimeseriesGraph.js +83 -32
  18. package/src/ui/js/components/HeaderSummary.js +11 -5
  19. package/src/ui/js/components/ServerStatus.js +19 -7
  20. package/src/ui/js/components/SessionLabels.js +197 -49
  21. package/src/ui/js/components/SessionWedges.js +44 -24
  22. package/src/ui/js/components/StatusRing.js +20 -8
  23. package/src/ui/js/components/grids.js +14 -7
  24. package/src/ui/js/index.js +23 -19
  25. package/src/ui/js/main.js +53 -21
  26. package/src/ui/js/util/DependencyManager.js +5 -5
  27. package/src/ui/js/util/EventEmitter.js +11 -7
  28. package/src/ui/js/util/WebsocketClient.js +28 -22
  29. package/src/ui/js/util/constants.js +2 -2
  30. package/src/ui/js/util/drawing.js +58 -28
  31. package/src/websocket-interface/WebsocketClient.js +15 -15
  32. package/src/websocket-interface/WebsocketInterface.js +30 -22
  33. package/src/websocket-interface/WtrSession.js +49 -33
  34. package/src/websocket-interface/httpServer.js +24 -14
  35. package/src/websocket-interface/sessionManager.js +16 -13
  36. package/src/websocket-interface/util.js +1 -1
  37. package/src/websocket-interface/websocketApi.js +51 -34
  38. package/src/websocket-interface/websocketServer.js +9 -9
  39. package/start.js +1 -1
  40. package/.eslintrc +0 -29
@@ -0,0 +1,3 @@
1
+ # Ignore artifacts:
2
+ build
3
+ coverage
package/.prettierrc ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "semi": false,
3
+ "printWidth": 110
4
+ }
package/changelog.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## 1.1.4 - 2025/08/14
2
+
3
+ Fixed a crash that occurred when the status UI requests a file that doesn't exist.
4
+
5
+ ## 1.1.3 - 2025/08/13
6
+
7
+ This change consisted entirely of updating dependencies and cleaning up code. There should be no user facing changes with this version.
8
+
9
+ - Updated dependencies to remove all npm audit warnings
10
+ - Added prettier for code formatting
11
+
1
12
  ## 1.1.0 - 2024/04/07
2
13
 
3
14
  Increased default security.
@@ -21,7 +21,7 @@ This is all abstracted away behind the websocket-interface, which is explained i
21
21
 
22
22
  ## Core Logic: The lsp and websocket events
23
23
 
24
- LSP: `initailze` / WS: `init`
24
+ LSP: `initialize` / WS: `init`
25
25
 
26
26
  Whenever a new editor instance or front end client connects to the websocket, a websocket session is created for that client. These
27
27
  can be seen as the labeled blue wedges around the ring in the status UI. When the lsp client from the editor sends an
@@ -0,0 +1,28 @@
1
+ import js from "@eslint/js"
2
+ import globals from "globals"
3
+ import { defineConfig } from "eslint/config"
4
+
5
+ export default defineConfig([
6
+ {
7
+ files: ["**/*.{js,mjs,cjs}"],
8
+ plugins: { js },
9
+ extends: ["js/recommended"],
10
+ languageOptions: { globals: { ...globals.node, ...globals.browser } },
11
+ },
12
+ {
13
+ rules: {
14
+ semi: ["error", "never"],
15
+ indent: ["error", 2],
16
+ "no-multiple-empty-lines": ["error", { max: 1 }],
17
+ "no-unused-vars": 1,
18
+ "no-return-assign": 0,
19
+ "multiline-ternary": 0,
20
+ "object-curly-spacing": 0,
21
+ "object-property-newline": 0,
22
+ "object-curly-newline": 0,
23
+ quotes: 0,
24
+ "quote-props": 0,
25
+ "import/no-absolute-path": 0,
26
+ },
27
+ },
28
+ ])
package/package.json CHANGED
@@ -1,17 +1,19 @@
1
1
  {
2
2
  "name": "websocket-text-relay",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
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
- "lint": "eslint .",
10
- "test": "vitest run --coverage",
11
- "test-ui": "vitest --ui"
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"
12
14
  },
13
15
  "type": "module",
14
- "main": "index.js",
16
+ "main": "./src/index.js",
15
17
  "directories": {
16
18
  "doc": "docs"
17
19
  },
@@ -34,12 +36,15 @@
34
36
  },
35
37
  "homepage": "https://github.com/niels4/websocket-text-relay#readme",
36
38
  "devDependencies": {
37
- "@vitest/coverage-v8": "^1.4.0",
38
- "@vitest/ui": "^1.4.0",
39
- "eslint": "^8.57.0",
40
- "vitest": "^1.4.0"
39
+ "@eslint/js": "^9.33.0",
40
+ "@vitest/coverage-v8": "^3.2.4",
41
+ "@vitest/ui": "^3.2.4",
42
+ "eslint": "^9.33.0",
43
+ "globals": "^16.3.0",
44
+ "prettier": "3.6.2",
45
+ "vitest": "^3.2.4"
41
46
  },
42
47
  "dependencies": {
43
- "ws": "^8.16.0"
48
+ "ws": "^8.18.3"
44
49
  }
45
50
  }
@@ -1,47 +1,49 @@
1
- import { JsonRpcInterface } from "./src/language-server/JsonRpcInterface.js"
2
- import { WebsocketInterface } from "./src/websocket-interface/WebsocketInterface.js"
1
+ import { JsonRpcInterface } from "./language-server/JsonRpcInterface.js"
2
+ import { WebsocketInterface } from "./websocket-interface/WebsocketInterface.js"
3
3
 
4
4
  const DEFAULT_WS_PORT = 38378
5
5
 
6
6
  const resolvePort = (portParam) => {
7
- if (portParam != null) { return portParam }
8
- if (process.env["websocket_text_relay_port"] != null) { return process.env["websocket_text_relay_port"] }
7
+ if (portParam != null) {
8
+ return portParam
9
+ }
10
+ if (process.env["websocket_text_relay_port"] != null) {
11
+ return process.env["websocket_text_relay_port"]
12
+ }
9
13
  return DEFAULT_WS_PORT
10
14
  }
11
15
 
12
- const resolveIoStreams = ({inputStreamParam, outputStreamParam}) => {
13
- const inputStream = inputStreamParam || process.stdin
14
- const outputStream = outputStreamParam || process.stdout
15
- return {inputStream, outputStream}
16
- }
17
-
18
16
  const fileProtocolPrefix = "file://"
19
17
 
20
18
  const getNormalizedFileName = (uri) => {
21
- if (uri.startsWith(fileProtocolPrefix)) { return uri.substring(fileProtocolPrefix.length) }
19
+ if (uri.startsWith(fileProtocolPrefix)) {
20
+ return uri.substring(fileProtocolPrefix.length)
21
+ }
22
22
  return uri
23
23
  }
24
24
 
25
25
  const lspInitializeResponse = {
26
26
  serverInfo: {
27
27
  name: "Websocket Text Relay LSP Server",
28
- version: "1.0.0"
28
+ version: "1.0.0",
29
29
  },
30
30
  capabilities: {
31
- textDocumentSync: 1 // full text sync
32
- }
31
+ textDocumentSync: 1, // full text sync
32
+ },
33
33
  }
34
34
 
35
35
  export const startLanguageServer = (options = {}) => {
36
- const {inputStream: inputStreamParam, outputStream: outputStreamParam, port: portParam} = options
36
+ const { inputStream: inputStreamParam, outputStream: outputStreamParam, port: portParam } = options
37
+ const outputStream = outputStreamParam || process.stdout
38
+ const inputStream = inputStreamParam || process.stdin
37
39
 
38
- const jsonRpc = new JsonRpcInterface(resolveIoStreams({inputStreamParam, outputStreamParam}))
40
+ const jsonRpc = new JsonRpcInterface({ inputStream, outputStream })
39
41
 
40
- const wsInterface = new WebsocketInterface({port: resolvePort(portParam)})
42
+ const wsInterface = new WebsocketInterface({ port: resolvePort(portParam) })
41
43
 
42
- wsInterface.emitter.on('message', (message) => {
44
+ wsInterface.emitter.on("message", (message) => {
43
45
  if (message.method === "watch-editor-active-files") {
44
- jsonRpc.sendNotification("wtr/update-active-files", {files: message.files})
46
+ jsonRpc.sendNotification("wtr/update-active-files", { files: message.files })
45
47
  }
46
48
  })
47
49
 
@@ -50,7 +52,7 @@ export const startLanguageServer = (options = {}) => {
50
52
  method: "init",
51
53
  name: params.clientInfo?.name || "Unnamed Editor",
52
54
  editorPid: params.processId,
53
- lsPid: process.pid
55
+ lsPid: process.pid,
54
56
  }
55
57
 
56
58
  const initOptions = params.initializationOptions || {}
@@ -74,19 +76,19 @@ export const startLanguageServer = (options = {}) => {
74
76
  process.exit(0)
75
77
  })
76
78
 
77
- jsonRpc.onNotification('wtr/update-open-files', ({files}) => {
79
+ jsonRpc.onNotification("wtr/update-open-files", ({ files }) => {
78
80
  wsInterface.sendOpenFileList(files)
79
81
  })
80
82
 
81
83
  jsonRpc.onNotification("textDocument/didOpen", (params) => {
82
84
  const file = getNormalizedFileName(params.textDocument.uri)
83
85
  const contents = params.textDocument.text
84
- wsInterface.sendText({file, contents})
86
+ wsInterface.sendText({ file, contents })
85
87
  })
86
88
 
87
89
  jsonRpc.onNotification("textDocument/didChange", (params) => {
88
90
  const file = getNormalizedFileName(params.textDocument.uri)
89
91
  const contents = params.contentChanges[0]?.text
90
- wsInterface.sendText({file, contents})
92
+ wsInterface.sendText({ file, contents })
91
93
  })
92
94
  }
@@ -1,11 +1,19 @@
1
- import { EventEmitter } from 'node:events'
1
+ import { EventEmitter } from "node:events"
2
2
  import { LspReader } from "./LspReader.js"
3
- import { writeNotification, writeRequest, writeResponse } from './LspWriter.js'
4
- import { invalidRequestErrorCode, invalidRequestErrorMessage, methodNotFoundErrorCode, methodNotFoundErrorMessage, unexpectedNotificationErrorCode,
5
- unexpectedNotificationErrorMessage, unexpectedRequestErrorCode, unexpectedRequestErrorMessage } from './constants.js'
3
+ import { writeNotification, writeRequest, writeResponse } from "./LspWriter.js"
4
+ import {
5
+ invalidRequestErrorCode,
6
+ invalidRequestErrorMessage,
7
+ methodNotFoundErrorCode,
8
+ methodNotFoundErrorMessage,
9
+ unexpectedNotificationErrorCode,
10
+ unexpectedNotificationErrorMessage,
11
+ unexpectedRequestErrorCode,
12
+ unexpectedRequestErrorMessage,
13
+ } from "./constants.js"
6
14
 
7
15
  export class JsonRpcInterface {
8
- constructor ({inputStream, outputStream}) {
16
+ constructor({ inputStream, outputStream }) {
9
17
  this.inputStream = inputStream
10
18
  this.outputStream = outputStream
11
19
 
@@ -16,40 +24,44 @@ export class JsonRpcInterface {
16
24
  this.lspInputStream = new LspReader()
17
25
  this.inputStream.pipe(this.lspInputStream)
18
26
 
19
- this.lspInputStream.on('data', this._handleIncomingLspMessage.bind(this))
27
+ this.lspInputStream.on("data", this._handleIncomingLspMessage.bind(this))
20
28
 
21
- this.lspInputStream.on('parse-error', (error) => {
29
+ this.lspInputStream.on("parse-error", (error) => {
22
30
  writeResponse(this.outputStream, null, error)
23
31
  })
24
32
  }
25
33
 
26
- onNotification (method, handler) {
34
+ onNotification(method, handler) {
27
35
  if (this.notificationHandlers.has(method)) {
28
- throw new Error("Can only register one notification handler at a time. Duplicate method handlers for " + method)
36
+ throw new Error(
37
+ "Can only register one notification handler at a time. Duplicate method handlers for " + method,
38
+ )
29
39
  }
30
40
  this.notificationHandlers.set(method, handler)
31
41
  }
32
42
 
33
- removeNotificationHandler (method) {
43
+ removeNotificationHandler(method) {
34
44
  return this.notificationHandlers.delete(method)
35
45
  }
36
46
 
37
- onRequest (method, handler) {
47
+ onRequest(method, handler) {
38
48
  if (this.requestHandlers.has(method)) {
39
- throw new Error("Can only register one request handler at a time. Duplicate method handlers for " + method)
49
+ throw new Error(
50
+ "Can only register one request handler at a time. Duplicate method handlers for " + method,
51
+ )
40
52
  }
41
53
  this.requestHandlers.set(method, handler)
42
54
  }
43
55
 
44
- removeRequestHandler (method) {
56
+ removeRequestHandler(method) {
45
57
  return this.requestHandlers.delete(method)
46
58
  }
47
59
 
48
- sendNotification (method, params) {
60
+ sendNotification(method, params) {
49
61
  writeNotification(this.outputStream, method, params)
50
62
  }
51
63
 
52
- sendRequest (method, params) {
64
+ sendRequest(method, params) {
53
65
  const responseHandler = {}
54
66
  const promise = new Promise((resolve, reject) => {
55
67
  responseHandler.resolve = resolve
@@ -60,8 +72,8 @@ export class JsonRpcInterface {
60
72
  return promise
61
73
  }
62
74
 
63
- _handleIncomingLspMessage (message) {
64
- const {method, params, id, error, result} = message
75
+ _handleIncomingLspMessage(message) {
76
+ const { method, params, id, error, result } = message
65
77
  if (id != null) {
66
78
  if (method != null) {
67
79
  this._handleIncomingRequest(method, params, id)
@@ -75,47 +87,65 @@ export class JsonRpcInterface {
75
87
  this._handleIncomingNotification(method, params)
76
88
  return
77
89
  }
78
- const rpcError = {code: invalidRequestErrorCode, message: invalidRequestErrorMessage}
79
- this.events.emit('rpc-error', rpcError)
90
+ const rpcError = { code: invalidRequestErrorCode, message: invalidRequestErrorMessage }
91
+ this.events.emit("rpc-error", rpcError)
80
92
  writeResponse(this.outputStream, null, rpcError)
81
93
  }
82
94
 
83
- _handleIncomingNotification (method, params) {
95
+ _handleIncomingNotification(method, params) {
84
96
  const handler = this.notificationHandlers.get(method)
85
97
  if (!handler) {
86
- const errorObject = {code: methodNotFoundErrorCode, message: methodNotFoundErrorMessage, data: {method}}
98
+ const errorObject = {
99
+ code: methodNotFoundErrorCode,
100
+ message: methodNotFoundErrorMessage,
101
+ data: { method },
102
+ }
87
103
  this.events.emit("notification-error", errorObject)
88
104
  return
89
105
  }
90
106
  try {
91
107
  handler(params)
92
108
  } catch (error) {
93
- const errorObject = {code: unexpectedNotificationErrorCode, message: unexpectedNotificationErrorMessage, data: {method, params, error: error.message}}
109
+ const errorObject = {
110
+ code: unexpectedNotificationErrorCode,
111
+ message: unexpectedNotificationErrorMessage,
112
+ data: { method, params, error: error.message },
113
+ }
94
114
  this.events.emit("notification-error", errorObject)
95
115
  }
96
116
  }
97
117
 
98
- async _handleIncomingRequest (method, params, id) {
118
+ async _handleIncomingRequest(method, params, id) {
99
119
  const handler = this.requestHandlers.get(method)
100
120
  if (!handler) {
101
- const errorObject = {code: methodNotFoundErrorCode, message: methodNotFoundErrorMessage, data: {method}}
121
+ const errorObject = {
122
+ code: methodNotFoundErrorCode,
123
+ message: methodNotFoundErrorMessage,
124
+ data: { method },
125
+ }
102
126
  writeResponse(this.outputStream, id, errorObject)
103
- this.events.emit("request-error", {id, error: errorObject})
127
+ this.events.emit("request-error", { id, error: errorObject })
104
128
  return
105
129
  }
106
130
  try {
107
131
  const result = await handler(params)
108
132
  writeResponse(this.outputStream, id, null, result)
109
133
  } catch (error) {
110
- const errorObject = {code: unexpectedRequestErrorCode, message: unexpectedRequestErrorMessage, data: {method, params, error: error.message}}
111
- this.events.emit("request-error", {id, error: errorObject})
134
+ const errorObject = {
135
+ code: unexpectedRequestErrorCode,
136
+ message: unexpectedRequestErrorMessage,
137
+ data: { method, params, error: error.message },
138
+ }
139
+ this.events.emit("request-error", { id, error: errorObject })
112
140
  writeResponse(this.outputStream, id, errorObject)
113
141
  }
114
142
  }
115
143
 
116
- _handleRequestResponse (id, error, result) {
144
+ _handleRequestResponse(id, error, result) {
117
145
  const responseHandler = this.outstandingRequests.get(id)
118
- if (!responseHandler) { return }
146
+ if (!responseHandler) {
147
+ return
148
+ }
119
149
  this.outstandingRequests.delete(id)
120
150
  if (error != null) {
121
151
  responseHandler.reject(error)