chrome-dev/site.go
2025-11-10 08:18:54 +01:00

117 lines
2.7 KiB
Go

package main
import (
// External
"github.com/chromedp/cdproto/target"
"github.com/chromedp/chromedp"
"github.com/google/uuid"
// Standard
"context"
"fmt"
"log"
"os/exec"
"sync"
)
var (
sites map[string]*Site
siteLock *sync.Mutex
)
type Site struct {
UUID string
URL string
Watch string
WatchLoop *exec.Cmd `json:"-"`
StopLoop bool
Context context.Context `json:"-"`
}
func init() {
siteLock = &sync.Mutex{}
sites = make(map[string]*Site)
}
func NewSite(wsURL, url, watch string) (s Site, err error) {
s.UUID = uuid.NewString()
s.URL = url
s.Watch = watch
allocatorContext, _ := chromedp.NewRemoteAllocator(context.Background(), wsURL)
// Used to optionally enable debugging output.
var opts []chromedp.ContextOption
if flagVerbose {
opts = append(opts, chromedp.WithDebugf(log.Printf))
}
s.Context, _ = chromedp.NewContext(allocatorContext, opts...)
// A tab is started up and navigated to in order to get a context for this instance.
if err = chromedp.Run(
s.Context,
chromedp.Navigate(url),
); err != nil {
err = fmt.Errorf("Failed getting body of %s: %v", url, err)
return
}
siteLock.Lock()
sites[s.UUID] = &s
defer siteLock.Unlock()
// chromedp.Run crashes something really hard and undetectable with recover().
// ListenBrowser gives the possibility to cancel the watch loop.
chromedp.ListenBrowser(s.Context, func(ev any) {
_, event1 := ev.(*target.EventDetachedFromTarget)
_, event2 := ev.(*target.EventTargetCrashed)
_, event3 := ev.(*target.EventTargetDestroyed)
if event1 || event2 || event3 {
log.Printf("Stopping loop for %s\n", s.UUID)
s.StopLoop = true
if s.WatchLoop != nil {
log.Printf("Killing inotifywait for %s\n", s.UUID)
s.WatchLoop.Process.Kill()
}
}
})
// Start watching file/dir for changes.
go s.watchLoop()
return
}
func (s *Site) watchLoop() {
for !s.StopLoop {
log.Println("Starting watching " + s.Watch)
s.WatchLoop = exec.Command("inotifywait", "-e", "close_write", s.Watch)
if err := s.WatchLoop.Run(); err != nil {
log.Println(err)
}
s.ReloadCSS()
}
log.Printf("Stopping watch loop [%s]\n", s.UUID)
}
func (s *Site) ReloadCSS() {
var buf []byte
cssReloadScript := chromedp.Evaluate(`
(()=>{
const stylesheets = document.querySelectorAll('link[rel="stylesheet"]')
for (const ss of stylesheets) {
const url = URL.parse(ss.href, location.protocol + '//' + location.host)
const nextReloadCounter = parseInt(url.searchParams.get('reload') || 0) + 1
url.searchParams.set('reload', nextReloadCounter)
ss.href = url.toString()
}
})()
`, &buf)
err := chromedp.Run(s.Context, cssReloadScript)
if err != nil {
fmt.Println(err)
}
}