github.com/MontFerret/ferret@v0.18.0/pkg/drivers/cdp/network/streams.go (about) 1 package network 2 3 import ( 4 "context" 5 "encoding/base64" 6 "sync/atomic" 7 8 "github.com/mafredri/cdp" 9 "github.com/mafredri/cdp/protocol/network" 10 "github.com/mafredri/cdp/protocol/page" 11 "github.com/mafredri/cdp/rpcc" 12 "github.com/rs/zerolog" 13 14 "github.com/MontFerret/ferret/pkg/drivers/cdp/eval" 15 "github.com/MontFerret/ferret/pkg/drivers/cdp/events" 16 "github.com/MontFerret/ferret/pkg/drivers/cdp/templates" 17 "github.com/MontFerret/ferret/pkg/runtime/core" 18 rtEvents "github.com/MontFerret/ferret/pkg/runtime/events" 19 "github.com/MontFerret/ferret/pkg/runtime/values" 20 ) 21 22 type NavigationEventStream struct { 23 logger zerolog.Logger 24 client *cdp.Client 25 tail atomic.Value 26 onFrame page.FrameNavigatedClient 27 onDoc page.NavigatedWithinDocumentClient 28 } 29 30 func newNavigationEventStream( 31 logger zerolog.Logger, 32 client *cdp.Client, 33 onFrame page.FrameNavigatedClient, 34 onDoc page.NavigatedWithinDocumentClient, 35 ) rtEvents.Stream { 36 es := new(NavigationEventStream) 37 es.logger = logger 38 es.client = client 39 es.onFrame = onFrame 40 es.onDoc = onDoc 41 42 return es 43 } 44 45 func (s *NavigationEventStream) Read(ctx context.Context) <-chan rtEvents.Message { 46 ch := make(chan rtEvents.Message) 47 48 go func() { 49 defer close(ch) 50 51 for { 52 select { 53 case <-ctx.Done(): 54 return 55 case <-s.onDoc.Ready(): 56 if ctx.Err() != nil { 57 return 58 } 59 60 repl, err := s.onDoc.Recv() 61 62 if err != nil { 63 ch <- rtEvents.WithErr(err) 64 s.logger.Trace().Err(err).Msg("failed to read data from within document navigation event stream") 65 66 return 67 } 68 69 evt := NavigationEvent{ 70 URL: repl.URL, 71 FrameID: repl.FrameID, 72 } 73 74 s.logger.Trace(). 75 Str("url", evt.URL). 76 Str("frame_id", string(evt.FrameID)). 77 Str("type", evt.MimeType). 78 Msg("received withing document navigation event") 79 80 s.tail.Store(evt) 81 82 ch <- rtEvents.WithValue(&evt) 83 case <-s.onFrame.Ready(): 84 if ctx.Err() != nil { 85 return 86 } 87 88 repl, err := s.onFrame.Recv() 89 90 if err != nil { 91 ch <- rtEvents.WithErr(err) 92 s.logger.Trace().Err(err).Msg("failed to read data from frame navigation event stream") 93 94 return 95 } 96 97 evt := NavigationEvent{ 98 URL: repl.Frame.URL, 99 FrameID: repl.Frame.ID, 100 MimeType: repl.Frame.MimeType, 101 } 102 103 s.logger.Trace(). 104 Str("url", evt.URL). 105 Str("frame_id", string(evt.FrameID)). 106 Str("type", evt.MimeType). 107 Msg("received frame navigation event") 108 109 s.tail.Store(evt) 110 111 ch <- rtEvents.WithValue(&evt) 112 } 113 } 114 }() 115 116 return ch 117 } 118 119 func (s *NavigationEventStream) Close(ctx context.Context) error { 120 val := s.tail.Load() 121 122 evt, ok := val.(NavigationEvent) 123 124 if !ok || evt.FrameID == "" { 125 // TODO: err? 126 return nil 127 } 128 129 _ = s.onFrame.Close() 130 _ = s.onDoc.Close() 131 132 s.logger.Trace(). 133 Str("frame_id", string(evt.FrameID)). 134 Str("frame_url", evt.URL). 135 Msg("creating frame execution context") 136 137 ec, err := eval.Create(ctx, s.logger, s.client, evt.FrameID) 138 139 if err != nil { 140 s.logger.Trace(). 141 Err(err). 142 Str("frame_id", string(evt.FrameID)). 143 Str("frame_url", evt.URL). 144 Msg("failed to create frame execution context") 145 146 return err 147 } 148 149 s.logger.Trace(). 150 Str("frame_id", string(evt.FrameID)). 151 Str("frame_url", evt.URL). 152 Msg("starting polling DOM ready event") 153 154 _, err = events.NewEvalWaitTask( 155 ec, 156 templates.DOMReady(), 157 events.DefaultPolling, 158 ).Run(ctx) 159 160 if err != nil { 161 s.logger.Trace(). 162 Err(err). 163 Str("frame_id", string(evt.FrameID)). 164 Str("frame_url", evt.URL). 165 Msg("failed to poll DOM ready event") 166 167 return err 168 } 169 170 s.logger.Trace(). 171 Str("frame_id", string(evt.FrameID)). 172 Str("frame_url", evt.URL). 173 Msg("DOM is ready. Navigation has completed") 174 175 return nil 176 } 177 178 func newRequestWillBeSentStream(logger zerolog.Logger, input network.RequestWillBeSentClient) rtEvents.Stream { 179 return events.NewEventStream(input, func(_ context.Context, stream rpcc.Stream) (core.Value, error) { 180 repl, err := stream.(network.RequestWillBeSentClient).Recv() 181 182 if err != nil { 183 logger.Trace().Err(err).Msg("failed to read data from request event stream") 184 185 return values.None, nil 186 } 187 188 var frameID string 189 190 if repl.FrameID != nil { 191 frameID = string(*repl.FrameID) 192 } 193 194 logger.Trace(). 195 Str("url", repl.Request.URL). 196 Str("document_url", repl.DocumentURL). 197 Str("frame_id", frameID). 198 Interface("data", repl.Request). 199 Msg("received request event") 200 201 return toDriverRequest(repl.Request), nil 202 }) 203 } 204 205 func newResponseReceivedReader(logger zerolog.Logger, client *cdp.Client, input network.ResponseReceivedClient) rtEvents.Stream { 206 return events.NewEventStream(input, func(ctx context.Context, stream rpcc.Stream) (core.Value, error) { 207 repl, err := stream.(network.ResponseReceivedClient).Recv() 208 209 if err != nil { 210 logger.Trace().Err(err).Msg("failed to read data from request event stream") 211 212 return values.None, nil 213 } 214 215 var frameID string 216 217 if repl.FrameID != nil { 218 frameID = string(*repl.FrameID) 219 } 220 221 logger.Trace(). 222 Str("url", repl.Response.URL). 223 Str("frame_id", frameID). 224 Str("request_id", string(repl.RequestID)). 225 Interface("data", repl.Response). 226 Msg("received response event") 227 228 var body []byte 229 230 resp, err := client.Network.GetResponseBody(ctx, network.NewGetResponseBodyArgs(repl.RequestID)) 231 232 if err == nil { 233 body = make([]byte, 0, 0) 234 235 if resp.Base64Encoded { 236 body, err = base64.StdEncoding.DecodeString(resp.Body) 237 238 if err != nil { 239 logger.Warn(). 240 Str("url", repl.Response.URL). 241 Str("frame_id", frameID). 242 Str("request_id", string(repl.RequestID)). 243 Interface("data", repl.Response). 244 Msg("failed to decode response body") 245 } 246 } else { 247 body = []byte(resp.Body) 248 } 249 } else { 250 logger.Warn(). 251 Str("url", repl.Response.URL). 252 Str("frame_id", frameID). 253 Str("request_id", string(repl.RequestID)). 254 Interface("data", repl.Response). 255 Msg("failed to get response body") 256 } 257 258 return toDriverResponse(repl.Response, body), nil 259 }) 260 }