|
1 | 1 | import { Logger, field } from "@coder/logger"
|
2 | 2 | import * as express from "express"
|
3 | 3 | import * as fs from "fs"
|
| 4 | +import * as hijackresponse from "hijackresponse" |
4 | 5 | import * as path from "path"
|
5 | 6 | import * as semver from "semver"
|
6 | 7 | import * as pluginapi from "../../typings/pluginapi"
|
@@ -86,7 +87,12 @@ export class PluginAPI {
|
86 | 87 | */
|
87 | 88 | public mount(r: express.Router): void {
|
88 | 89 | 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 | + }) |
90 | 96 | }
|
91 | 97 | }
|
92 | 98 |
|
@@ -241,3 +247,58 @@ function q(s: string | undefined): string {
|
241 | 247 | }
|
242 | 248 | return JSON.stringify(s)
|
243 | 249 | }
|
| 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 | +} |
0 commit comments