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  }