github.com/MontFerret/ferret@v0.18.0/pkg/drivers/cdp/driver.go (about)

     1  package cdp
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  
     7  	"github.com/mafredri/cdp"
     8  	"github.com/mafredri/cdp/devtool"
     9  	"github.com/mafredri/cdp/protocol/browser"
    10  	"github.com/mafredri/cdp/protocol/target"
    11  	"github.com/mafredri/cdp/rpcc"
    12  	"github.com/mafredri/cdp/session"
    13  	"github.com/pkg/errors"
    14  
    15  	"github.com/MontFerret/ferret/pkg/drivers"
    16  	"github.com/MontFerret/ferret/pkg/runtime/logging"
    17  )
    18  
    19  const DriverName = "cdp"
    20  const BlankPageURL = "about:blank"
    21  
    22  var defaultViewport = &drivers.Viewport{
    23  	Width:  1600,
    24  	Height: 900,
    25  }
    26  
    27  type Driver struct {
    28  	mu        sync.Mutex
    29  	dev       *devtool.DevTools
    30  	conn      *rpcc.Conn
    31  	client    *cdp.Client
    32  	session   *session.Manager
    33  	contextID browser.ContextID
    34  	options   *Options
    35  }
    36  
    37  func NewDriver(opts ...Option) *Driver {
    38  	drv := new(Driver)
    39  	drv.options = NewOptions(opts)
    40  	drv.dev = devtool.New(drv.options.Address)
    41  
    42  	return drv
    43  }
    44  
    45  func (drv *Driver) Name() string {
    46  	return drv.options.Name
    47  }
    48  
    49  func (drv *Driver) Open(ctx context.Context, params drivers.Params) (drivers.HTMLPage, error) {
    50  	logger := logging.FromContext(ctx)
    51  
    52  	conn, err := drv.createConnection(ctx, params.KeepCookies)
    53  
    54  	if err != nil {
    55  		logger.Error().
    56  			Err(err).
    57  			Str("driver", drv.options.Name).
    58  			Msg("failed to create a new connection")
    59  
    60  		return nil, err
    61  	}
    62  
    63  	return LoadHTMLPage(ctx, conn, drv.setDefaultParams(params))
    64  }
    65  
    66  func (drv *Driver) Parse(ctx context.Context, params drivers.ParseParams) (drivers.HTMLPage, error) {
    67  	logger := logging.FromContext(ctx)
    68  
    69  	conn, err := drv.createConnection(ctx, true)
    70  
    71  	if err != nil {
    72  		logger.Error().
    73  			Err(err).
    74  			Str("driver", drv.options.Name).
    75  			Msg("failed to create a new connection")
    76  
    77  		return nil, err
    78  	}
    79  
    80  	return LoadHTMLPageWithContent(ctx, conn, drv.setDefaultParams(drivers.Params{
    81  		URL:         BlankPageURL,
    82  		UserAgent:   "",
    83  		KeepCookies: params.KeepCookies,
    84  		Cookies:     params.Cookies,
    85  		Headers:     params.Headers,
    86  		Viewport:    params.Viewport,
    87  	}), params.Content)
    88  }
    89  
    90  func (drv *Driver) Close() error {
    91  	drv.mu.Lock()
    92  	defer drv.mu.Unlock()
    93  
    94  	if drv.session != nil {
    95  		drv.session.Close()
    96  
    97  		return drv.conn.Close()
    98  	}
    99  
   100  	return nil
   101  }
   102  
   103  func (drv *Driver) createConnection(ctx context.Context, keepCookies bool) (*rpcc.Conn, error) {
   104  	err := drv.init(ctx)
   105  
   106  	if err != nil {
   107  		return nil, errors.Wrap(err, "initialize driver")
   108  	}
   109  
   110  	// Args for a new target belonging to the browser context
   111  	createTargetArgs := target.NewCreateTargetArgs(BlankPageURL)
   112  
   113  	if !drv.options.KeepCookies && !keepCookies {
   114  		// Set it to an incognito mode
   115  		createTargetArgs.SetBrowserContextID(drv.contextID)
   116  	}
   117  
   118  	// New target
   119  	createTarget, err := drv.client.Target.CreateTarget(ctx, createTargetArgs)
   120  
   121  	if err != nil {
   122  		return nil, errors.Wrap(err, "create a browser target")
   123  	}
   124  
   125  	// Connect to target using the existing websocket connection.
   126  	conn, err := drv.session.Dial(ctx, createTarget.TargetID)
   127  
   128  	if err != nil {
   129  		return nil, errors.Wrap(err, "establish a new connection")
   130  	}
   131  
   132  	return conn, nil
   133  }
   134  
   135  func (drv *Driver) setDefaultParams(params drivers.Params) drivers.Params {
   136  	if params.Viewport == nil {
   137  		params.Viewport = defaultViewport
   138  	}
   139  
   140  	return drivers.SetDefaultParams(drv.options.Options, params)
   141  }
   142  
   143  func (drv *Driver) init(ctx context.Context) error {
   144  	drv.mu.Lock()
   145  	defer drv.mu.Unlock()
   146  
   147  	if drv.session == nil {
   148  		ver, err := drv.dev.Version(ctx)
   149  
   150  		if err != nil {
   151  			return errors.Wrap(err, "failed to initialize driver")
   152  		}
   153  
   154  		dialOpts := make([]rpcc.DialOption, 0, 2)
   155  
   156  		if drv.options.Connection != nil {
   157  			if drv.options.Connection.BufferSize > 0 {
   158  				dialOpts = append(dialOpts, rpcc.WithWriteBufferSize(drv.options.Connection.BufferSize))
   159  			}
   160  
   161  			if drv.options.Connection.Compression {
   162  				dialOpts = append(dialOpts, rpcc.WithCompression())
   163  			}
   164  		}
   165  
   166  		bconn, err := rpcc.DialContext(
   167  			ctx,
   168  			ver.WebSocketDebuggerURL,
   169  			dialOpts...,
   170  		)
   171  
   172  		if err != nil {
   173  			return errors.Wrap(err, "failed to initialize driver")
   174  		}
   175  
   176  		bc := cdp.NewClient(bconn)
   177  
   178  		sess, err := session.NewManager(bc)
   179  
   180  		if err != nil {
   181  			bconn.Close()
   182  
   183  			return errors.Wrap(err, "failed to initialize driver")
   184  		}
   185  
   186  		drv.conn = bconn
   187  		drv.client = bc
   188  		drv.session = sess
   189  
   190  		if drv.options.KeepCookies {
   191  			return nil
   192  		}
   193  
   194  		createCtx, err := bc.Target.CreateBrowserContext(ctx, &target.CreateBrowserContextArgs{})
   195  
   196  		if err != nil {
   197  			bconn.Close()
   198  			sess.Close()
   199  
   200  			return err
   201  		}
   202  
   203  		drv.contextID = createCtx.BrowserContextID
   204  	}
   205  
   206  	return nil
   207  }