websocket-text-relay 1.1.2 → 1.1.3
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/.prettierignore +3 -0
- package/.prettierrc +4 -0
- package/changelog.md +7 -0
- package/docs/code-structure.md +1 -1
- package/eslint.config.js +28 -0
- package/package.json +15 -10
- package/{index.js → src/index.js} +23 -21
- package/src/language-server/JsonRpcInterface.js +59 -29
- package/src/language-server/JsonRpcInterface.test.js +102 -72
- package/src/language-server/LspReader.js +30 -20
- package/src/language-server/LspReader.test.js +147 -65
- package/src/language-server/LspWriter.js +5 -5
- package/src/language-server/LspWriter.test.js +31 -24
- package/src/ui/css/fonts.css +0 -1
- package/src/ui/css/main.css +7 -7
- package/src/ui/index.html +4 -4
- package/src/ui/js/components/ActivityTimeseriesGraph.js +83 -32
- package/src/ui/js/components/HeaderSummary.js +11 -5
- package/src/ui/js/components/ServerStatus.js +19 -7
- package/src/ui/js/components/SessionLabels.js +197 -49
- package/src/ui/js/components/SessionWedges.js +44 -24
- package/src/ui/js/components/StatusRing.js +20 -8
- package/src/ui/js/components/grids.js +14 -7
- package/src/ui/js/index.js +23 -19
- package/src/ui/js/main.js +53 -21
- package/src/ui/js/util/DependencyManager.js +5 -5
- package/src/ui/js/util/EventEmitter.js +11 -7
- package/src/ui/js/util/WebsocketClient.js +28 -22
- package/src/ui/js/util/constants.js +2 -2
- package/src/ui/js/util/drawing.js +58 -28
- package/src/websocket-interface/WebsocketClient.js +15 -15
- package/src/websocket-interface/WebsocketInterface.js +30 -22
- package/src/websocket-interface/WtrSession.js +49 -33
- package/src/websocket-interface/httpServer.js +11 -10
- package/src/websocket-interface/sessionManager.js +16 -13
- package/src/websocket-interface/util.js +1 -1
- package/src/websocket-interface/websocketApi.js +51 -34
- package/src/websocket-interface/websocketServer.js +9 -9
- package/start.js +1 -1
- package/.eslintrc +0 -29
package/.prettierignore
ADDED
package/.prettierrc
ADDED
package/changelog.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## 1.1.3 - 2025/08/13
|
|
2
|
+
|
|
3
|
+
This change consisted entirely of updating dependencies and cleaning up code. There should be no user facing changes with this version.
|
|
4
|
+
|
|
5
|
+
- Updated dependencies to remove all npm audit warnings
|
|
6
|
+
- Added prettier for code formatting
|
|
7
|
+
|
|
1
8
|
## 1.1.0 - 2024/04/07
|
|
2
9
|
|
|
3
10
|
Increased default security.
|
package/docs/code-structure.md
CHANGED
|
@@ -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: `
|
|
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
|
package/eslint.config.js
ADDED
|
@@ -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.
|
|
3
|
+
"version": "1.1.3",
|
|
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
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
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
|
-
"@
|
|
38
|
-
"@vitest/
|
|
39
|
-
"
|
|
40
|
-
"
|
|
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.
|
|
48
|
+
"ws": "^8.18.3"
|
|
44
49
|
}
|
|
45
50
|
}
|
|
@@ -4,44 +4,46 @@ import { WebsocketInterface } from "./src/websocket-interface/WebsocketInterface
|
|
|
4
4
|
const DEFAULT_WS_PORT = 38378
|
|
5
5
|
|
|
6
6
|
const resolvePort = (portParam) => {
|
|
7
|
-
if (portParam != null) {
|
|
8
|
-
|
|
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)) {
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
1
|
+
import { EventEmitter } from "node:events"
|
|
2
2
|
import { LspReader } from "./LspReader.js"
|
|
3
|
-
import { writeNotification, writeRequest, writeResponse } from
|
|
4
|
-
import {
|
|
5
|
-
|
|
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
|
|
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(
|
|
27
|
+
this.lspInputStream.on("data", this._handleIncomingLspMessage.bind(this))
|
|
20
28
|
|
|
21
|
-
this.lspInputStream.on(
|
|
29
|
+
this.lspInputStream.on("parse-error", (error) => {
|
|
22
30
|
writeResponse(this.outputStream, null, error)
|
|
23
31
|
})
|
|
24
32
|
}
|
|
25
33
|
|
|
26
|
-
onNotification
|
|
34
|
+
onNotification(method, handler) {
|
|
27
35
|
if (this.notificationHandlers.has(method)) {
|
|
28
|
-
throw new Error(
|
|
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
|
|
43
|
+
removeNotificationHandler(method) {
|
|
34
44
|
return this.notificationHandlers.delete(method)
|
|
35
45
|
}
|
|
36
46
|
|
|
37
|
-
onRequest
|
|
47
|
+
onRequest(method, handler) {
|
|
38
48
|
if (this.requestHandlers.has(method)) {
|
|
39
|
-
throw new Error(
|
|
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
|
|
56
|
+
removeRequestHandler(method) {
|
|
45
57
|
return this.requestHandlers.delete(method)
|
|
46
58
|
}
|
|
47
59
|
|
|
48
|
-
sendNotification
|
|
60
|
+
sendNotification(method, params) {
|
|
49
61
|
writeNotification(this.outputStream, method, params)
|
|
50
62
|
}
|
|
51
63
|
|
|
52
|
-
sendRequest
|
|
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
|
|
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(
|
|
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
|
|
95
|
+
_handleIncomingNotification(method, params) {
|
|
84
96
|
const handler = this.notificationHandlers.get(method)
|
|
85
97
|
if (!handler) {
|
|
86
|
-
const errorObject = {
|
|
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 = {
|
|
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
|
|
118
|
+
async _handleIncomingRequest(method, params, id) {
|
|
99
119
|
const handler = this.requestHandlers.get(method)
|
|
100
120
|
if (!handler) {
|
|
101
|
-
const errorObject = {
|
|
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 = {
|
|
111
|
-
|
|
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
|
|
144
|
+
_handleRequestResponse(id, error, result) {
|
|
117
145
|
const responseHandler = this.outstandingRequests.get(id)
|
|
118
|
-
if (!responseHandler) {
|
|
146
|
+
if (!responseHandler) {
|
|
147
|
+
return
|
|
148
|
+
}
|
|
119
149
|
this.outstandingRequests.delete(id)
|
|
120
150
|
if (error != null) {
|
|
121
151
|
responseHandler.reject(error)
|