-
Notifications
You must be signed in to change notification settings - Fork 0
/
chrome.go
128 lines (115 loc) · 3.43 KB
/
chrome.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package preview
import (
context "context"
"fmt"
"log"
"time"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/proto"
"github.com/ysmood/gson"
)
func (s *Server) do(id string) {
s.work <- id
}
type resp struct {
id string
data []byte
err error
}
func (s *Server) listen() {
//only one unit of work at a time
var queue []string
done := make(chan resp)
busy := false
for {
select {
case w := <-s.work:
queue = append(queue, w)
s.logger.Info("got work", "id", w)
case res := <-done:
busy = false
if res.err != nil {
status := s.rdb.Set(context.Background(), res.id, res.err.Error(), 5*time.Second)
pubstatus := s.rdb.Publish(context.Background(), res.id, res.err.Error())
s.logger.Info("work failed", "id", res.id, "err", res.err, "set_err", status.Err(), "publish_err", pubstatus.Err())
} else {
status := s.rdb.Set(context.Background(), res.id, encode(res.data), s.cacheTTL)
pubstatus := s.rdb.Publish(context.Background(), res.id, "done")
s.logger.Info("work done", "id", res.id, "err", res.err, "set_err", status.Err(), "publish_err", pubstatus.Err())
}
}
//TODO: more than 1 worker?
if !busy && len(queue) > 0 {
busy = true
next := queue[0]
go s.doWork(next, done)
queue = queue[1:]
s.logger.Info("starting work", "id", next, "remaining", queue)
}
}
}
func (s *Server) doWork(id string, done chan resp) {
res, err := s.generateSnapshot(s.previewURL + "/" + id)
s.logger.Info("work completed", "err", err)
done <- resp{
id: id,
data: res,
err: err,
}
}
func (s *Server) generateSnapshot(url string) ([]byte, error) {
page, err := s.browser.Page(proto.TargetCreateTarget{})
if err != nil {
return nil, fmt.Errorf("error creating page: %w", err)
}
_, err = page.SetExtraHeaders([]string{"X-CUSTOM-AUTH-KEY", s.authKey})
if err != nil {
return nil, fmt.Errorf("error setting extra headers: %w", err)
}
log.Println("page load ok")
page.SetViewport(&proto.EmulationSetDeviceMetricsOverride{
Width: 520,
Height: 250,
})
err = page.Navigate(url)
if err != nil {
return nil, fmt.Errorf("error navigating to page: %w", err)
}
log.Println("navigated to page")
_, err = page.Race().ElementFunc(func(p *rod.Page) (*rod.Element, error) {
res, err := page.Evaluate(rod.Eval(`(s, n) => document.querySelectorAll(s).length > n`, "div", 10))
if err != nil {
s.logger.Info("error querying for all divs", "err", err)
return nil, &rod.ElementNotFoundError{}
}
if !res.Value.Bool() {
return nil, &rod.ElementNotFoundError{}
}
res, err = page.Evaluate(rod.Eval(`(s) => document.querySelectorAll(s).length > 0`, "#images_loaded"))
if err != nil {
s.logger.Info("error querying for #images_loaded", "err", err)
return nil, &rod.ElementNotFoundError{}
}
if !res.Value.Bool() {
return nil, &rod.ElementNotFoundError{}
}
return &rod.Element{}, nil
}).Element("#has-error").Handle(func(e *rod.Element) error {
str, err := e.Attribute("value")
// can't do much aobut this err here other than log it
if err != nil {
s.logger.Info("error encountered looking for value attribute", "err", err)
return fmt.Errorf("unexpected server error: %v", err)
}
return fmt.Errorf("generate preview failed: %v", *str)
}).Do()
if err != nil {
return nil, err
}
log.Println("race done")
buf, err := page.Screenshot(true, &proto.PageCaptureScreenshot{
Format: proto.PageCaptureScreenshotFormatWebp,
Quality: gson.Int(100),
})
return buf, err
}