Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3e82a47

Browse files
committed
plugin.ts: Hijack plugin HTML responses and inject app-overlay.js
1 parent 6bc111f commit 3e82a47

File tree

7 files changed

+96
-15
lines changed

7 files changed

+96
-15
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"env-paths": "^2.2.0",
7777
"express": "^5.0.0-alpha.8",
7878
"fs-extra": "^9.0.1",
79+
"hijackresponse": "^5.0.0",
7980
"http-proxy": "^1.18.0",
8081
"httpolyglot": "^0.1.2",
8182
"js-yaml": "^3.13.1",

src/node/plugin.ts

+62-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Logger, field } from "@coder/logger"
22
import * as express from "express"
33
import * as fs from "fs"
4+
import * as hijackresponse from "hijackresponse"
45
import * as path from "path"
56
import * as semver from "semver"
67
import * as pluginapi from "../../typings/pluginapi"
@@ -86,7 +87,12 @@ export class PluginAPI {
8687
*/
8788
public mount(r: express.Router): void {
8889
for (const [, p] of this.plugins) {
89-
r.use(`${p.routerPath}`, p.router())
90+
const pr = p.router()
91+
r.use(`${p.routerPath}`, (req, res, next) => {
92+
// All HTML responses need the overlay javascript injected.
93+
tryInjectOverlay(res)
94+
pr(req, res, next)
95+
})
9096
}
9197
}
9298

@@ -241,3 +247,58 @@ function q(s: string | undefined): string {
241247
}
242248
return JSON.stringify(s)
243249
}
250+
251+
function tryInjectOverlay(res: express.Response) {
252+
hijackresponse.default(res).then((hj) => {
253+
if (!res.get("Content-Type").includes("text/html")) {
254+
hj.readable.pipe(hj.writable)
255+
return
256+
}
257+
injectOverlay(res, hj)
258+
})
259+
}
260+
261+
// injectOverlay injects the script tag for the overlay into the HTML response
262+
// in res.
263+
// A point of improvement is to make it stream instead of buffer the entire response.
264+
// See for example https://www.npmjs.com/package/stream-buffer-replace
265+
async function injectOverlay(res: express.Response, hj: hijackresponse.HJ): Promise<void> {
266+
res.removeHeader("Content-Length")
267+
268+
try {
269+
const bodyPromise = new Promise<string>((res, rej) => {
270+
hj.readable.on("close", rej)
271+
hj.writable.on("close", rej)
272+
hj.readable.on("error", rej)
273+
hj.writable.on("error", rej)
274+
275+
const chunks: Buffer[] = []
276+
hj.readable.on("data", (chunk: Buffer) => {
277+
chunks.push(chunk)
278+
})
279+
hj.readable.on("end", () => {
280+
res(String(Buffer.concat(chunks)))
281+
})
282+
})
283+
let body = await bodyPromise
284+
body = injectOverlayHTML(body)
285+
hj.writable.write(body)
286+
hj.writable.end()
287+
} catch (err) {
288+
hj.destroyAndRestore()
289+
res.status(500)
290+
res.json({ error: "overlay script injection failed" })
291+
}
292+
}
293+
294+
/**
295+
* injectOverlayString injects the app-overlay.js script tag
296+
* into the html.
297+
*/
298+
export function injectOverlayHTML(html: string): string {
299+
return html.replace(
300+
"</head>",
301+
` <script defer data-cfasync="false" src="./dist/app-overlay.js"></script>
302+
</head>`,
303+
)
304+
}

src/node/routes/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import { replaceTemplates } from "../http"
1515
import { PluginAPI } from "../plugin"
1616
import { getMediaMime, paths } from "../util"
1717
import { WebsocketRequest } from "../wsRouter"
18-
import * as domainProxy from "./domainProxy"
1918
import * as apps from "./apps"
19+
import * as domainProxy from "./domainProxy"
2020
import * as health from "./health"
2121
import * as login from "./login"
2222
import * as proxy from "./pathProxy"

test/plugin.test.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
import { logger } from "@coder/logger"
2+
import * as express from "express"
3+
import * as fs from "fs"
24
import { describe } from "mocha"
35
import * as path from "path"
4-
import { PluginAPI } from "../src/node/plugin"
56
import * as supertest from "supertest"
6-
import * as express from "express"
7+
import * as plugin from "../src/node/plugin"
78
import * as apps from "../src/node/routes/apps"
8-
import * as fs from "fs"
99
const fsp = fs.promises
1010

1111
/**
1212
* Use $LOG_LEVEL=debug to see debug logs.
1313
*/
1414
describe("plugin", () => {
15-
let papi: PluginAPI
15+
let papi: plugin.PluginAPI
1616
let app: express.Application
1717
let agent: supertest.SuperAgentTest
1818

1919
before(async () => {
20-
papi = new PluginAPI(logger, path.resolve(__dirname, "test-plugin") + ":meow")
20+
papi = new plugin.PluginAPI(logger, path.resolve(__dirname, "test-plugin") + ":meow")
2121
await papi.loadPlugins()
2222

2323
app = express.default()
@@ -54,9 +54,10 @@ describe("plugin", () => {
5454
})
5555

5656
it("/test-plugin/test-app", async () => {
57-
const indexHTML = await fsp.readFile(path.join(__dirname, "test-plugin/public/index.html"), {
57+
let indexHTML = await fsp.readFile(path.join(__dirname, "test-plugin/public/index.html"), {
5858
encoding: "utf8",
5959
})
60+
indexHTML = plugin.injectOverlayHTML(indexHTML)
6061
await agent.get("/test-plugin/test-app").expect(200, indexHTML)
6162
})
6263
})

test/test-plugin/public/index.html

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
<!DOCTYPE html>
22
<html lang="en">
3-
<head>
4-
<meta charset="UTF-8">
5-
<title>Test Plugin</title>
6-
</head>
7-
<body>
8-
<p>Welcome to the test plugin!</p>
9-
</body>
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>Test Plugin</title>
6+
</head>
7+
<body>
8+
<p>Welcome to the test plugin!</p>
9+
</body>
1010
</html>

typings/hijackresponse/index.d.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
declare module "hijackresponse" {
2+
import * as express from "express"
3+
import * as stream from "stream"
4+
5+
function e(res: express.Response): Promise<HJ>
6+
export default e
7+
8+
export interface HJ {
9+
readable: stream.Readable
10+
writable: stream.Writable
11+
destroyAndRestore: () => void
12+
}
13+
}

yarn.lock

+5
Original file line numberDiff line numberDiff line change
@@ -3867,6 +3867,11 @@ hex-color-regex@^1.1.0:
38673867
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
38683868
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
38693869

3870+
hijackresponse@^5.0.0:
3871+
version "5.0.0"
3872+
resolved "https://registry.yarnpkg.com/hijackresponse/-/hijackresponse-5.0.0.tgz#03174e70e582a244034af9254c71e7d0a7a763cd"
3873+
integrity sha512-9ErmDCl4BdTEgLTQh36ZFP62EXPeKyDd68K/FmQoQFaV3i2QY9l/kacf9Ygw+iuSvF/qsNIkwUz6NOCIZAaqMw==
3874+
38703875
hmac-drbg@^1.0.0:
38713876
version "1.0.1"
38723877
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"

0 commit comments

Comments
 (0)