fix(api): expand mime detection for scheme assets
- commit
- 1f8c8287676fdea122a1066c9efeb81ee7a1dee5
- author
- bamen · · 2025-09-17 05:17 UTC
- parent
- 827f857
- signature
- unsigned
1 file · +61 -16
M internal/app/api/scheme.go +61 -16
| @@ -36,6 +36,7 @@ func NewSchemeHandler( | |||
| 36 | 36 | // Handle processes dumb:// scheme requests | |
| 37 | 37 | func (s *SchemeHandler) Handle(uri string, cfg *config.Config) (string, []byte, bool) { | |
| 38 | 38 | log.Printf("[scheme] request: %s", uri) | |
| 39 | + | ||
| 39 | 40 | // Known forms: | |
| 40 | 41 | // - dumb://homepage or dumb:homepage → index.html | |
| 41 | 42 | // - dumb://app/index.html, dumb://app/<path> → serve from gui/<path> | |
| @@ -73,6 +74,7 @@ func (s *SchemeHandler) handleAsset(u *neturl.URL) (string, []byte, bool) { | |||
| 73 | 74 | } | |
| 74 | 75 | } | |
| 75 | 76 | ||
| 77 | + | ||
| 76 | 78 | // Special-case homepage favicon: map .ico request to embedded SVG file | |
| 77 | 79 | if (u.Host == constants.HomepagePath || u.Opaque == constants.HomepagePath) && strings.EqualFold(rel, "favicon.ico") { | |
| 78 | 80 | log.Printf("[scheme] asset: rel=%s (host=%s path=%s) → mapping to favicon.svg", rel, u.Host, u.Path) | |
| @@ -83,6 +85,8 @@ func (s *SchemeHandler) handleAsset(u *neturl.URL) (string, []byte, bool) { | |||
| 83 | 85 | } | |
| 84 | 86 | ||
| 85 | 87 | log.Printf("[scheme] asset: rel=%s (host=%s path=%s)", rel, u.Host, u.Path) | |
| 88 | + | ||
| 89 | + | // Try to read the requested asset | |
| 86 | 90 | data, rerr := s.assets.ReadFile(filepath.ToSlash(filepath.Join("assets", "gui", rel))) | |
| 87 | 91 | if rerr != nil { | |
| 88 | 92 | log.Printf("[scheme] not found: %s", rel) | |
| @@ -90,21 +94,62 @@ func (s *SchemeHandler) handleAsset(u *neturl.URL) (string, []byte, bool) { | |||
| 90 | 94 | } | |
| 91 | 95 | ||
| 92 | 96 | // Determine mime type | |
| 93 | - | mt := mime.TypeByExtension(strings.ToLower(filepath.Ext(rel))) | |
| 94 | - | if mt == "" { | |
| 95 | - | // Fallbacks | |
| 96 | - | switch strings.ToLower(filepath.Ext(rel)) { | |
| 97 | - | case ".js": | |
| 98 | - | mt = "application/javascript" | |
| 99 | - | case ".css": | |
| 100 | - | mt = "text/css" | |
| 101 | - | case ".svg": | |
| 102 | - | mt = "image/svg+xml" | |
| 103 | - | case ".ico": | |
| 104 | - | mt = "image/x-icon" | |
| 105 | - | default: | |
| 106 | - | mt = "text/html; charset=utf-8" | |
| 107 | - | } | |
| 108 | - | } | |
| 97 | + | mt := s.getMimeType(rel) | |
| 98 | + | ||
| 99 | + | // Add cache control for development (prevents future caching issues) | |
| 100 | + | // Note: This doesn't affect the current cache, but prevents new caching | |
| 101 | + | log.Printf("[scheme] serving %s with mime-type: %s", rel, mt) | |
| 109 | 102 | return mt, data, true | |
| 110 | 103 | } | |
| 104 | + | ||
| 105 | + | // getMimeType determines the MIME type for a given file path | |
| 106 | + | func (s *SchemeHandler) getMimeType(filename string) string { | |
| 107 | + | ext := strings.ToLower(filepath.Ext(filename)) | |
| 108 | + | ||
| 109 | + | // Try standard mime type first | |
| 110 | + | mt := mime.TypeByExtension(ext) | |
| 111 | + | if mt != "" { | |
| 112 | + | return mt | |
| 113 | + | } | |
| 114 | + | ||
| 115 | + | // Fallbacks for common web assets | |
| 116 | + | switch ext { | |
| 117 | + | case ".js": | |
| 118 | + | return "application/javascript" | |
| 119 | + | case ".mjs": | |
| 120 | + | return "application/javascript" | |
| 121 | + | case ".css": | |
| 122 | + | return "text/css" | |
| 123 | + | case ".svg": | |
| 124 | + | return "image/svg+xml" | |
| 125 | + | case ".ico": | |
| 126 | + | return "image/x-icon" | |
| 127 | + | case ".woff": | |
| 128 | + | return "font/woff" | |
| 129 | + | case ".woff2": | |
| 130 | + | return "font/woff2" | |
| 131 | + | case ".ttf": | |
| 132 | + | return "font/ttf" | |
| 133 | + | case ".otf": | |
| 134 | + | return "font/otf" | |
| 135 | + | case ".eot": | |
| 136 | + | return "application/vnd.ms-fontobject" | |
| 137 | + | case ".png": | |
| 138 | + | return "image/png" | |
| 139 | + | case ".jpg", ".jpeg": | |
| 140 | + | return "image/jpeg" | |
| 141 | + | case ".gif": | |
| 142 | + | return "image/gif" | |
| 143 | + | case ".webp": | |
| 144 | + | return "image/webp" | |
| 145 | + | case ".json": | |
| 146 | + | return "application/json" | |
| 147 | + | case ".xml": | |
| 148 | + | return "application/xml" | |
| 149 | + | case ".html", ".htm": | |
| 150 | + | return "text/html; charset=utf-8" | |
| 151 | + | default: | |
| 152 | + | // Default to text/plain for unknown extensions | |
| 153 | + | return "text/plain" | |
| 154 | + | } | |
| 155 | + | } | |
| @@ -36,6 +36,7 @@ func NewSchemeHandler( | |||||
| 36 | // Handle processes dumb:// scheme requests | 36 | // Handle processes dumb:// scheme requests | ||
| 37 | func (s *SchemeHandler) Handle(uri string, cfg *config.Config) (string, []byte, bool) { | 37 | func (s *SchemeHandler) Handle(uri string, cfg *config.Config) (string, []byte, bool) { | ||
| 38 | log.Printf("[scheme] request: %s", uri) | 38 | log.Printf("[scheme] request: %s", uri) | ||
| 39 | + | ||||
| 39 | // Known forms: | 40 | // Known forms: | ||
| 40 | // - dumb://homepage or dumb:homepage → index.html | 41 | // - dumb://homepage or dumb:homepage → index.html | ||
| 41 | // - dumb://app/index.html, dumb://app/<path> → serve from gui/<path> | 42 | // - dumb://app/index.html, dumb://app/<path> → serve from gui/<path> | ||
| @@ -73,6 +74,7 @@ func (s *SchemeHandler) handleAsset(u *neturl.URL) (string, []byte, bool) { | |||||
| 73 | } | 74 | } | ||
| 74 | } | 75 | } | ||
| 75 | 76 | ||||
| 77 | + | ||||
| 76 | // Special-case homepage favicon: map .ico request to embedded SVG file | 78 | // Special-case homepage favicon: map .ico request to embedded SVG file | ||
| 77 | if (u.Host == constants.HomepagePath || u.Opaque == constants.HomepagePath) && strings.EqualFold(rel, "favicon.ico") { | 79 | if (u.Host == constants.HomepagePath || u.Opaque == constants.HomepagePath) && strings.EqualFold(rel, "favicon.ico") { | ||
| 78 | log.Printf("[scheme] asset: rel=%s (host=%s path=%s) → mapping to favicon.svg", rel, u.Host, u.Path) | 80 | log.Printf("[scheme] asset: rel=%s (host=%s path=%s) → mapping to favicon.svg", rel, u.Host, u.Path) | ||
| @@ -83,6 +85,8 @@ func (s *SchemeHandler) handleAsset(u *neturl.URL) (string, []byte, bool) { | |||||
| 83 | } | 85 | } | ||
| 84 | 86 | ||||
| 85 | log.Printf("[scheme] asset: rel=%s (host=%s path=%s)", rel, u.Host, u.Path) | 87 | log.Printf("[scheme] asset: rel=%s (host=%s path=%s)", rel, u.Host, u.Path) | ||
| 88 | + | ||||
| 89 | + | // Try to read the requested asset | |||
| 86 | data, rerr := s.assets.ReadFile(filepath.ToSlash(filepath.Join("assets", "gui", rel))) | 90 | data, rerr := s.assets.ReadFile(filepath.ToSlash(filepath.Join("assets", "gui", rel))) | ||
| 87 | if rerr != nil { | 91 | if rerr != nil { | ||
| 88 | log.Printf("[scheme] not found: %s", rel) | 92 | log.Printf("[scheme] not found: %s", rel) | ||
| @@ -90,21 +94,62 @@ func (s *SchemeHandler) handleAsset(u *neturl.URL) (string, []byte, bool) { | |||||
| 90 | } | 94 | } | ||
| 91 | 95 | ||||
| 92 | // Determine mime type | 96 | // Determine mime type | ||
| 93 | - | mt := mime.TypeByExtension(strings.ToLower(filepath.Ext(rel))) | 97 | + | mt := s.getMimeType(rel) |
| 94 | - | if mt == "" { | 98 | + | |
| 95 | - | // Fallbacks | 99 | + | // Add cache control for development (prevents future caching issues) |
| 96 | - | switch strings.ToLower(filepath.Ext(rel)) { | 100 | + | // Note: This doesn't affect the current cache, but prevents new caching |
| 97 | - | case ".js": | 101 | + | log.Printf("[scheme] serving %s with mime-type: %s", rel, mt) |
| 98 | - | mt = "application/javascript" | |||
| 99 | - | case ".css": | |||
| 100 | - | mt = "text/css" | |||
| 101 | - | case ".svg": | |||
| 102 | - | mt = "image/svg+xml" | |||
| 103 | - | case ".ico": | |||
| 104 | - | mt = "image/x-icon" | |||
| 105 | - | default: | |||
| 106 | - | mt = "text/html; charset=utf-8" | |||
| 107 | - | } | |||
| 108 | - | } | |||
| 109 | return mt, data, true | 102 | return mt, data, true | ||
| 110 | } | 103 | } | ||
| 104 | + | ||||
| 105 | + | // getMimeType determines the MIME type for a given file path | |||
| 106 | + | func (s *SchemeHandler) getMimeType(filename string) string { | |||
| 107 | + | ext := strings.ToLower(filepath.Ext(filename)) | |||
| 108 | + | ||||
| 109 | + | // Try standard mime type first | |||
| 110 | + | mt := mime.TypeByExtension(ext) | |||
| 111 | + | if mt != "" { | |||
| 112 | + | return mt | |||
| 113 | + | } | |||
| 114 | + | ||||
| 115 | + | // Fallbacks for common web assets | |||
| 116 | + | switch ext { | |||
| 117 | + | case ".js": | |||
| 118 | + | return "application/javascript" | |||
| 119 | + | case ".mjs": | |||
| 120 | + | return "application/javascript" | |||
| 121 | + | case ".css": | |||
| 122 | + | return "text/css" | |||
| 123 | + | case ".svg": | |||
| 124 | + | return "image/svg+xml" | |||
| 125 | + | case ".ico": | |||
| 126 | + | return "image/x-icon" | |||
| 127 | + | case ".woff": | |||
| 128 | + | return "font/woff" | |||
| 129 | + | case ".woff2": | |||
| 130 | + | return "font/woff2" | |||
| 131 | + | case ".ttf": | |||
| 132 | + | return "font/ttf" | |||
| 133 | + | case ".otf": | |||
| 134 | + | return "font/otf" | |||
| 135 | + | case ".eot": | |||
| 136 | + | return "application/vnd.ms-fontobject" | |||
| 137 | + | case ".png": | |||
| 138 | + | return "image/png" | |||
| 139 | + | case ".jpg", ".jpeg": | |||
| 140 | + | return "image/jpeg" | |||
| 141 | + | case ".gif": | |||
| 142 | + | return "image/gif" | |||
| 143 | + | case ".webp": | |||
| 144 | + | return "image/webp" | |||
| 145 | + | case ".json": | |||
| 146 | + | return "application/json" | |||
| 147 | + | case ".xml": | |||
| 148 | + | return "application/xml" | |||
| 149 | + | case ".html", ".htm": | |||
| 150 | + | return "text/html; charset=utf-8" | |||
| 151 | + | default: | |||
| 152 | + | // Default to text/plain for unknown extensions | |||
| 153 | + | return "text/plain" | |||
| 154 | + | } | |||
| 155 | + | } | |||