-
Notifications
You must be signed in to change notification settings - Fork 48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Better type safety with Target.SendMessageToTarget #10
Comments
Oh, that's interesting. Thanks for bringing this to my attention. I've never tried to use the I think we definitely can do better here, and I have a few ideas but they will need some more thought. Some ideas:
By the way, doesn't |
Wow thank you! This probably explains why my sessions have been a bit strange. My goal was to reuse a single websocket connection concurrently, but I'm not seeing how it's possible to get Which in that case I'd vote for:
Since it provides the best parity between the protocol and this library. |
It seems you can communicate with any target as long as you have attached to it once before, so it should be fully possible to reuse one connection for all targets, it just means that communication must be synchronized between Here's what I used, slightly modified from your original code sample: Multi-target communicationbctx, err := c.Target.CreateBrowserContext(ctx)
if err != nil {
return err
}
width := 940
height := 500
tar1, err := c.Target.CreateTarget(ctx, &cdpcmd.TargetCreateTargetArgs{
BrowserContextID: &bctx.BrowserContextID,
URL: "about:blank",
Width: &width,
Height: &height,
})
if err != nil {
return err
}
tar2, err := c.Target.CreateTarget(ctx, &cdpcmd.TargetCreateTargetArgs{
BrowserContextID: &bctx.BrowserContextID,
URL: "about:blank",
Width: &width,
Height: &height,
})
if err != nil {
return err
}
recvMessage, err := c.Target.ReceivedMessageFromTarget(ctx)
if err != nil {
return err
}
go func() {
defer recvMessage.Close()
for {
ev, err := recvMessage.Recv()
if err != nil {
log.Println(err)
return
}
log.Printf("%#v", ev)
}
}()
attached, err := c.Target.AttachToTarget(ctx, &cdpcmd.TargetAttachToTargetArgs{
TargetID: tar1.TargetID,
})
if err != nil {
return err
} else if !attached.Success {
return errors.New("could not attach")
}
attached, err = c.Target.AttachToTarget(ctx, &cdpcmd.TargetAttachToTargetArgs{
TargetID: tar2.TargetID,
})
if err != nil {
return err
} else if !attached.Success {
return errors.New("could not attach")
}
err = c.Target.SendMessageToTarget(ctx, &cdpcmd.TargetSendMessageToTargetArgs{
TargetID: tar1.TargetID,
Message: `{"id":0,"method":"Page.enable","params":{}}`,
})
if err != nil {
return err
}
err = c.Target.SendMessageToTarget(ctx, &cdpcmd.TargetSendMessageToTargetArgs{
TargetID: tar2.TargetID,
Message: `{"id":0,"method":"Page.enable","params":{}}`,
})
if err != nil {
return err
} Log output
|
Actually, it seems my speculation about With the code I posted above, I can do the following without problems: c.Page.Enable(ctx)
c.Page.Navigate(ctx, cdpcmd.NewPageNavigateArgs("https://github.com"))
err = c.Target.SendMessageToTarget(ctx, &cdpcmd.TargetSendMessageToTargetArgs{
TargetID: tar1.TargetID,
Message: `{"id":1,"method":"Page.navigate","params":{"url":"https://www.google.com"}}`,
})
if err != nil {
return err
}
err = c.Target.SendMessageToTarget(ctx, &cdpcmd.TargetSendMessageToTargetArgs{
TargetID: tar2.TargetID,
Message: `{"id":1,"method":"Page.navigate","params":{"url":"https://www.duckduckgo.com"}}`,
})
if err != nil {
return err
} |
I got inspired and hacked together something that works with the current implementation. A bunch of code has been omitted for brevity, but it's based off earlier code samples. This logic could easily be encapsulated by the lib and exposed as a helper. Think Considering this, I think it would be beneficial to change Thoughts? Please note that this is prototype code, and e.g. // targetCodec sends messages to a target via the provided client.
type targetCodec struct {
ctx context.Context
client *cdp.Client
r chan []byte
targetID cdptype.TargetID
}
func (c *targetCodec) WriteRequest(r *rpcc.Request) error {
b, err := json.Marshal(r)
if err != nil {
return err
}
args := &cdpcmd.TargetSendMessageToTargetArgs{
TargetID: c.targetID,
Message: string(b),
}
return c.client.Target.SendMessageToTarget(c.ctx, args)
}
func (c *targetCodec) ReadResponse(r *rpcc.Response) error {
b := <-c.r
return json.Unmarshal(b, r)
}
func run() error {
// Setup conn, client...
// Start listening to target messages.
recvMessage, err := c.Target.ReceivedMessageFromTarget(ctx)
if err != nil {
return err
}
// Handle incoming messages from all targets.
tar1Ch := make(chan []byte, 1)
tar2Ch := make(chan []byte, 1)
go func() {
defer recvMessage.Close()
for {
ev, err := recvMessage.Recv()
if err != nil {
log.Println(err)
return
}
if ev.TargetID == tar1.TargetID {
tar1Ch <- []byte(ev.Message)
}
if ev.TargetID == tar2.TargetID {
tar2Ch <- []byte(ev.Message)
}
}
}()
// Attach to targets...
// Setup a new fake connection to target.
tar1Conn, err := rpcc.Dial(
"", // Ignore URL, we're not connecting anywhere.
rpcc.WithDialer(func(_ context.Context, _ string) (net.Conn, error) { return nil, nil }),
rpcc.WithCodec(func(conn io.ReadWriter) rpcc.Codec {
return &targetCodec{
ctx: ctx,
r: tar1Ch,
targetID: tar1.TargetID,
client: c,
}
}),
)
if err != nil {
return err
}
defer tar1Conn.Close()
// Create cdp client.
tar1Client := cdp.NewClient(tar1Conn)
// Use as any client.
loadEventFired, err := tar1Client.Page.LoadEventFired(ctx)
if err != nil {
return err
}
tar1Client.Page.Enable(ctx)
tar1Client.Page.Navigate(ctx, cdpcmd.NewPageNavigateArgs("https://www.google.com"))
loadEv, err := loadEventFired.Recv()
if err != nil {
return err
}
log.Printf("Load event at %v", loadEv.Timestamp.Time())
// ...
return nil
} |
Thanks for the implementation! I'll be going through this more in detail today. One thing I ran into with the single connection was that it was working pretty well on OSX, but on linux it failed immediately. I haven't traced the root cause, but it seems related to this attachTarget stuff. More details here: https://groups.google.com/a/chromium.org/d/msg/headless-dev/qqbZVZ2IwEw/P-yKHQ-JAwAJ I also realized that while multiple connections is less than ideal in a real-world setting, the number of targets will be quite limited (potentially 10s, but not 100s) on a single chrome process. |
@matthewmueller type safety is landing in #41! Check it out if you're interested 😄! |
Right now
Target.SendMessageToTarget
acceptsMessage
as a string, which is what the protocol expects, but that string is actually a subcommand, like this:I'm not exactly sure how this will work in Goland, but it'd be nice to somehow treat Message as a recursive command that runs
json.Marshal
on the command before sending it off.The text was updated successfully, but these errors were encountered: