package proxy
import (
"bytes"
"fmt"
"strings"
"sync"
"github.com/standardbeagle/agnt/internal/proxy/scripts"
)
var (
// Cache the instrumentation script since it never changes
cachedScript string
cachedScriptOnce sync.Once
)
// instrumentationScript returns JavaScript code for error and performance monitoring.
// The script is cached after first call for performance.
func instrumentationScript() string {
cachedScriptOnce.Do(func() {
cachedScript = scripts.GetCombinedScript()
})
return cachedScript
}
// InjectInstrumentation adds monitoring JavaScript to HTML responses.
// The wsPort parameter is deprecated and unused (kept for backward compatibility).
// The script now uses relative URLs via window.location.host.
func InjectInstrumentation(body []byte, wsPort int) []byte {
script := instrumentationScript()
// Try to inject before </head>
if idx := bytes.Index(body, []byte("</head>")); idx != -1 {
result := make([]byte, 0, len(body)+len(script))
result = append(result, body[:idx]...)
result = append(result, []byte(script)...)
result = append(result, body[idx:]...)
return result
}
// Try to inject after <head>
if idx := bytes.Index(body, []byte("<head>")); idx != -1 {
insertAt := idx + 6
result := make([]byte, 0, len(body)+len(script))
result = append(result, body[:insertAt]...)
result = append(result, []byte(script)...)
result = append(result, body[insertAt:]...)
return result
}
// Try to inject after <body>
if idx := bytes.Index(body, []byte("<body")); idx != -1 {
// Find the end of the body tag
endIdx := bytes.Index(body[idx:], []byte(">"))
if endIdx != -1 {
insertAt := idx + endIdx + 1
result := make([]byte, 0, len(body)+len(script))
result = append(result, body[:insertAt]...)
result = append(result, []byte(script)...)
result = append(result, body[insertAt:]...)
return result
}
}
// Try to inject after <html>
if idx := bytes.Index(body, []byte("<html")); idx != -1 {
endIdx := bytes.Index(body[idx:], []byte(">"))
if endIdx != -1 {
insertAt := idx + endIdx + 1
result := make([]byte, 0, len(body)+len(script))
result = append(result, body[:insertAt]...)
result = append(result, []byte(script)...)
result = append(result, body[insertAt:]...)
return result
}
}
// Last resort: prepend to body
result := make([]byte, 0, len(body)+len(script))
result = append(result, []byte(script)...)
result = append(result, body...)
return result
}
// InjectProxyMeta inserts a small script tag setting window.__devtool_proxy_id
// after the last </script> in the body. This is called after InjectInstrumentation
// so the instrumentation script's closing tag is the insertion point.
func InjectProxyMeta(body []byte, proxyID string) []byte {
meta := []byte(fmt.Sprintf("<script>window.__devtool_proxy_id=%q;</script>", proxyID))
marker := []byte("</script>")
if idx := bytes.LastIndex(body, marker); idx != -1 {
insertAt := idx + len(marker)
result := make([]byte, 0, len(body)+len(meta))
result = append(result, body[:insertAt]...)
result = append(result, meta...)
result = append(result, body[insertAt:]...)
return result
}
return body
}
// ShouldInject determines if JavaScript should be injected based on content type.
func ShouldInject(contentType string) bool {
contentType = strings.ToLower(contentType)
return strings.Contains(contentType, "text/html")
}