feat: add HTML injection to proxy responses

- Remove unused context import and simplify HTTP request creation in http_js.go - Update UI titles from "Portal Proxy" to "Portal Proxy Gateway" in index.html - Add HTML content type detection and injection in main_js.go to enable client-side features - Minor formatting fix in service-worker.js This enhances the web proxy by injecting necessary scripts into HTML responses, improving functionality for the portal gateway.

lemon-mint committed Oct 31, 2025 at 21:41 UTC 0b6433ba447b91f74c4079529bcd7a6b85ab65f6
5 files changed +78 -8
cmd/webclient/httpjs/http_js.go
+1 -5
@@ -3,7 +3,6 @@ package httpjs
3 import (
4 "bufio"
5 "bytes"
6 - "context"
6 "errors"
7 "io"
8 "net/http"
@@ -366,11 +365,8 @@ func JSRequestToHTTPRequest(jsReq js.Value) (*http.Request, error) {
365 bodyReader = bytes.NewReader([]byte{})
366 }
367
369 - ctx := context.Background()
370 - ctx = context.WithValue(ctx, "http.request.mode", jsReq.Get("mode").String())
371 -
368 // Create HTTP request
373 - httpReq, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
369 + httpReq, err := http.NewRequest(method, url, bodyReader)
370 if err != nil {
371 return nil, err
372 }
cmd/webclient/index.html
+2 -2
@@ -3,10 +3,10 @@
3 <head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 - <title>Portal Proxy</title>
6 + <title>Portal Proxy Gateway</title>
7 </head>
8 <body>
9 - <h1>Portal Proxy</h1>
9 + <h1>Portal Proxy Gateway</h1>
10 <hr/>
11 <p id="status">Please wait for the service worker to register...</p>
12 <script>
cmd/webclient/inject.go new
+62
@@ -0,0 +1,62 @@
1 +package main
2 +
3 +import (
4 + "bytes"
5 +
6 + "github.com/rs/zerolog/log"
7 + "golang.org/x/net/html"
8 +)
9 +
10 +func InjectHTML(body []byte) []byte {
11 + doc, err := html.Parse(bytes.NewReader(body))
12 + if err != nil {
13 + log.Error().Err(err).Msg("Failed to parse HTML")
14 + return body
15 + }
16 +
17 + // Find the head element
18 + var head *html.Node
19 + var crawler func(*html.Node)
20 + crawler = func(node *html.Node) {
21 + if node.Type == html.ElementNode && node.Data == "head" {
22 + head = node
23 + return
24 + }
25 + for child := node.FirstChild; child != nil; child = child.NextSibling {
26 + crawler(child)
27 + }
28 + }
29 + crawler(doc)
30 +
31 + if head != nil {
32 + // Create script element
33 + script := &html.Node{
34 + Type: html.ElementNode,
35 + Data: "script",
36 + Attr: []html.Attribute{},
37 + }
38 +
39 + // Add the script content
40 + scriptContent := &html.Node{
41 + Type: html.TextNode,
42 + Data: `var PORTAL_VERSION = "v1.0.0";`,
43 + }
44 + script.AppendChild(scriptContent)
45 +
46 + // Insert as the first child of head
47 + if head.FirstChild != nil {
48 + head.InsertBefore(script, head.FirstChild)
49 + } else {
50 + head.AppendChild(script)
51 + }
52 + }
53 +
54 + // Convert back to bytes
55 + var buf bytes.Buffer
56 + if err := html.Render(&buf, doc); err != nil {
57 + log.Error().Err(err).Msg("Failed to render HTML")
58 + return body
59 + }
60 +
61 + return buf.Bytes()
62 +}
cmd/webclient/main_js.go
+12
@@ -91,6 +91,18 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
91 w.Header()[key] = value
92 }
93
94 + if IsHTMLContentType(resp.Header.Get("Content-Type")) {
95 + w.WriteHeader(resp.StatusCode)
96 + body, err := io.ReadAll(resp.Body)
97 + if err != nil {
98 + log.Error().Err(err).Msg("Failed to read response body")
99 + return
100 + }
101 + body = InjectHTML(body)
102 + w.Write(body)
103 + return
104 + }
105 +
106 w.WriteHeader(resp.StatusCode)
107 io.Copy(w, resp.Body)
108 }
cmd/webclient/service-worker.js
+1 -1
@@ -70,7 +70,7 @@ self.addEventListener('fetch', async (e) => {
70 e.respondWith(new Response("ACK-e8c2c70c-ec4a-40b2-b8af-d5638264f831", { status: 200 }));
71 return;
72 }
73 -
73 +
74 if (__go_jshttp) {
75 e.respondWith((async () => {
76 try {