Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 055e0ef

Browse filesBrowse files
committed
Provide WsRouter to plugins
1 parent fb37473 commit 055e0ef
Copy full SHA for 055e0ef

8 files changed

+101-34Lines changed: 101 additions & 34 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/node/plugin.ts‎

Copy file name to clipboardExpand all lines: src/node/plugin.ts
+9-5Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as pluginapi from "../../typings/pluginapi"
77
import { version } from "./constants"
88
import { proxy } from "./proxy"
99
import * as util from "./util"
10+
import { Router as WsRouter, WebsocketRouter } from "./wsRouter"
1011
const fsp = fs.promises
1112

1213
/**
@@ -21,6 +22,7 @@ require("module")._load = function (request: string, parent: object, isMain: boo
2122
express,
2223
field,
2324
proxy,
25+
WsRouter,
2426
}
2527
}
2628
return originalLoad.apply(this, [request, parent, isMain])
@@ -103,14 +105,16 @@ export class PluginAPI {
103105
}
104106

105107
/**
106-
* mount mounts all plugin routers onto r.
108+
* mount mounts all plugin routers onto r and websocket routers onto wr.
107109
*/
108-
public mount(r: express.Router): void {
110+
public mount(r: express.Router, wr: express.Router): void {
109111
for (const [, p] of this.plugins) {
110-
if (!p.router) {
111-
continue
112+
if (p.router) {
113+
r.use(`${p.routerPath}`, p.router())
114+
}
115+
if (p.wsRouter) {
116+
wr.use(`${p.routerPath}`, (p.wsRouter() as WebsocketRouter).router)
112117
}
113-
r.use(`${p.routerPath}`, p.router())
114118
}
115119
}
116120

Collapse file

‎src/node/routes/index.ts‎

Copy file name to clipboardExpand all lines: src/node/routes/index.ts
+11-11Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,20 @@ import { promises as fs } from "fs"
66
import http from "http"
77
import * as path from "path"
88
import * as tls from "tls"
9+
import * as pluginapi from "../../../typings/pluginapi"
910
import { HttpCode, HttpError } from "../../common/http"
1011
import { plural } from "../../common/util"
1112
import { AuthType, DefaultedArgs } from "../cli"
1213
import { rootPath } from "../constants"
1314
import { Heart } from "../heart"
14-
import { replaceTemplates, redirect } from "../http"
15+
import { redirect, replaceTemplates } from "../http"
1516
import { PluginAPI } from "../plugin"
1617
import { getMediaMime, paths } from "../util"
17-
import { WebsocketRequest } from "../wsRouter"
1818
import * as apps from "./apps"
1919
import * as domainProxy from "./domainProxy"
2020
import * as health from "./health"
2121
import * as login from "./login"
22-
import * as proxy from "./pathProxy"
22+
import * as pathProxy from "./pathProxy"
2323
// static is a reserved keyword.
2424
import * as _static from "./static"
2525
import * as update from "./update"
@@ -104,21 +104,21 @@ export const register = async (
104104
wsApp.use("/", domainProxy.wsRouter.router)
105105

106106
app.all("/proxy/(:port)(/*)?", (req, res) => {
107-
proxy.proxy(req, res)
107+
pathProxy.proxy(req, res)
108108
})
109-
wsApp.get("/proxy/(:port)(/*)?", (req, res) => {
110-
proxy.wsProxy(req as WebsocketRequest)
109+
wsApp.get("/proxy/(:port)(/*)?", (req) => {
110+
pathProxy.wsProxy(req as pluginapi.WebsocketRequest)
111111
})
112112
// These two routes pass through the path directly.
113113
// So the proxied app must be aware it is running
114114
// under /absproxy/<someport>/
115115
app.all("/absproxy/(:port)(/*)?", (req, res) => {
116-
proxy.proxy(req, res, {
116+
pathProxy.proxy(req, res, {
117117
passthroughPath: true,
118118
})
119119
})
120-
wsApp.get("/absproxy/(:port)(/*)?", (req, res) => {
121-
proxy.wsProxy(req as WebsocketRequest, {
120+
wsApp.get("/absproxy/(:port)(/*)?", (req) => {
121+
pathProxy.wsProxy(req as pluginapi.WebsocketRequest, {
122122
passthroughPath: true,
123123
})
124124
})
@@ -146,7 +146,7 @@ export const register = async (
146146

147147
const papi = new PluginAPI(logger, process.env.CS_PLUGIN, process.env.CS_PLUGIN_PATH)
148148
await papi.loadPlugins()
149-
papi.mount(app)
149+
papi.mount(app, wsApp)
150150
app.use("/api/applications", apps.router(papi))
151151

152152
app.use(() => {
@@ -187,7 +187,7 @@ export const register = async (
187187

188188
const wsErrorHandler: express.ErrorRequestHandler = async (err, req, res, next) => {
189189
logger.error(`${err.message} ${err.stack}`)
190-
;(req as WebsocketRequest).ws.end()
190+
;(req as pluginapi.WebsocketRequest).ws.end()
191191
}
192192

193193
wsApp.use(wsErrorHandler)
Collapse file

‎src/node/routes/pathProxy.ts‎

Copy file name to clipboardExpand all lines: src/node/routes/pathProxy.ts
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { Request, Response } from "express"
22
import * as path from "path"
33
import qs from "qs"
4+
import * as pluginapi from "../../../typings/pluginapi"
45
import { HttpCode, HttpError } from "../../common/http"
56
import { normalize } from "../../common/util"
67
import { authenticated, ensureAuthenticated, redirect } from "../http"
78
import { proxy as _proxy } from "../proxy"
8-
import { WebsocketRequest } from "../wsRouter"
99

1010
const getProxyTarget = (req: Request, passthroughPath?: boolean): string => {
1111
if (passthroughPath) {
@@ -46,7 +46,7 @@ export function proxy(
4646
}
4747

4848
export function wsProxy(
49-
req: WebsocketRequest,
49+
req: pluginapi.WebsocketRequest,
5050
opts?: {
5151
passthroughPath?: boolean
5252
},
Collapse file

‎src/node/wsRouter.ts‎

Copy file name to clipboardExpand all lines: src/node/wsRouter.ts
+4-15Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as express from "express"
22
import * as expressCore from "express-serve-static-core"
33
import * as http from "http"
4-
import * as net from "net"
4+
import * as pluginapi from "../../typings/pluginapi"
55

66
export const handleUpgrade = (app: express.Express, server: http.Server): void => {
77
server.on("upgrade", (req, socket, head) => {
@@ -20,31 +20,20 @@ export const handleUpgrade = (app: express.Express, server: http.Server): void =
2020
})
2121
}
2222

23-
export interface WebsocketRequest extends express.Request {
24-
ws: net.Socket
25-
head: Buffer
26-
}
27-
28-
interface InternalWebsocketRequest extends WebsocketRequest {
23+
interface InternalWebsocketRequest extends pluginapi.WebsocketRequest {
2924
_ws_handled: boolean
3025
}
3126

32-
export type WebSocketHandler = (
33-
req: WebsocketRequest,
34-
res: express.Response,
35-
next: express.NextFunction,
36-
) => void | Promise<void>
37-
3827
export class WebsocketRouter {
3928
public readonly router = express.Router()
4029

41-
public ws(route: expressCore.PathParams, ...handlers: WebSocketHandler[]): void {
30+
public ws(route: expressCore.PathParams, ...handlers: pluginapi.WebSocketHandler[]): void {
4231
this.router.get(
4332
route,
4433
...handlers.map((handler) => {
4534
const wrapped: express.Handler = (req, res, next) => {
4635
;(req as InternalWebsocketRequest)._ws_handled = true
47-
return handler(req as WebsocketRequest, res, next)
36+
return handler(req as pluginapi.WebsocketRequest, res, next)
4837
}
4938
return wrapped
5039
}),
Collapse file

‎test/httpserver.ts‎

Copy file name to clipboardExpand all lines: test/httpserver.ts
+17Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import * as express from "express"
12
import * as http from "http"
23
import * as nodeFetch from "node-fetch"
4+
import Websocket from "ws"
35
import * as util from "../src/common/util"
46
import { ensureAddress } from "../src/node/app"
7+
import { handleUpgrade } from "../src/node/wsRouter"
58

69
// Perhaps an abstraction similar to this should be used in app.ts as well.
710
export class HttpServer {
@@ -39,6 +42,13 @@ export class HttpServer {
3942
})
4043
}
4144

45+
/**
46+
* Send upgrade requests to an Express app.
47+
*/
48+
public listenUpgrade(app: express.Express): void {
49+
handleUpgrade(app, this.hs)
50+
}
51+
4252
/**
4353
* close cleans up the server.
4454
*/
@@ -62,6 +72,13 @@ export class HttpServer {
6272
return nodeFetch.default(`${ensureAddress(this.hs)}${requestPath}`, opts)
6373
}
6474

75+
/**
76+
* Open a websocket against the requset path.
77+
*/
78+
public ws(requestPath: string): Websocket {
79+
return new Websocket(`${ensureAddress(this.hs).replace("http:", "ws:")}${requestPath}`)
80+
}
81+
6582
public port(): number {
6683
const addr = this.hs.address()
6784
if (addr && typeof addr === "object") {
Collapse file

‎test/plugin.test.ts‎

Copy file name to clipboardExpand all lines: test/plugin.test.ts
+12-1Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ describe("plugin", () => {
2121
await papi.loadPlugins(false)
2222

2323
const app = express.default()
24-
papi.mount(app)
24+
const wsApp = express.default()
25+
papi.mount(app, wsApp)
2526
app.use("/api/applications", apps.router(papi))
2627

2728
s = new httpserver.HttpServer()
2829
await s.listen(app)
30+
s.listenUpgrade(wsApp)
2931
})
3032

3133
afterAll(async () => {
@@ -70,4 +72,13 @@ describe("plugin", () => {
7072
const body = await resp.text()
7173
expect(body).toBe(indexHTML)
7274
})
75+
76+
it("/test-plugin/test-app (websocket)", async () => {
77+
const ws = s.ws("/test-plugin/test-app")
78+
const message = await new Promise((resolve) => {
79+
ws.once("message", (message) => resolve(message))
80+
})
81+
ws.terminate()
82+
expect(message).toBe("hello")
83+
})
7384
})
Collapse file

‎test/test-plugin/src/index.ts‎

Copy file name to clipboardExpand all lines: test/test-plugin/src/index.ts
+13Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import * as cs from "code-server"
22
import * as fspath from "path"
3+
import Websocket from "ws"
4+
5+
const wss = new Websocket.Server({ noServer: true })
36

47
export const plugin: cs.Plugin = {
58
displayName: "Test Plugin",
@@ -22,6 +25,16 @@ export const plugin: cs.Plugin = {
2225
return r
2326
},
2427

28+
wsRouter() {
29+
const wr = cs.WsRouter()
30+
wr.ws("/test-app", (req) => {
31+
wss.handleUpgrade(req, req.socket, req.head, (ws) => {
32+
ws.send("hello")
33+
})
34+
})
35+
return wr
36+
},
37+
2538
applications() {
2639
return [
2740
{
Collapse file

‎typings/pluginapi.d.ts‎

Copy file name to clipboardExpand all lines: typings/pluginapi.d.ts
+33Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
*/
44
import { field, Logger } from "@coder/logger"
55
import * as express from "express"
6+
import * as expressCore from "express-serve-static-core"
7+
import ProxyServer from "http-proxy"
8+
import * as net from "net"
69

710
/**
811
* Overlay
@@ -78,6 +81,27 @@ import * as express from "express"
7881
* ]
7982
*/
8083

84+
export interface WebsocketRequest extends express.Request {
85+
ws: net.Socket
86+
head: Buffer
87+
}
88+
89+
export type WebSocketHandler = (
90+
req: WebsocketRequest,
91+
res: express.Response,
92+
next: express.NextFunction,
93+
) => void | Promise<void>
94+
95+
export interface WebsocketRouter {
96+
readonly router: express.Router
97+
ws(route: expressCore.PathParams, ...handlers: WebSocketHandler[]): void
98+
}
99+
100+
/**
101+
* Create a router for websocket routes.
102+
*/
103+
export function WsRouter(): WebsocketRouter
104+
81105
/**
82106
* The Express import used by code-server.
83107
*
@@ -152,6 +176,15 @@ export interface Plugin {
152176
*/
153177
router?(): express.Router
154178

179+
/**
180+
* Returns the plugin's websocket router.
181+
*
182+
* Mounted at <code-sever-root>/<plugin-path>
183+
*
184+
* If not present, the plugin provides no websockets.
185+
*/
186+
wsRouter?(): WebsocketRouter
187+
155188
/**
156189
* code-server uses this to collect the list of applications that
157190
* the plugin can currently provide.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.