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 {