Skip to content

Commit a3473f1

Browse files
committed
Align translated ModCDP runtimes
1 parent 9454a54 commit a3473f1

49 files changed

Lines changed: 834 additions & 1045 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

go/modcdp/client/ModCDPClient.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ type upstreamTransportClient interface {
331331
Update(map[string]any)
332332
Connect() error
333333
Close() error
334-
Send(command string, params map[string]any, sessionID string, timeout ...time.Duration) (map[string]any, error)
334+
Send(command any, params map[string]any, sessionID string, timeout ...time.Duration) (map[string]any, error)
335335
ConfigForLauncher() LauncherConfig
336336
OnRecv(func(map[string]any)) func()
337337
OnClose(func(error)) func()
@@ -479,7 +479,10 @@ func (c *ModCDPClient) Configure(config Config) *ModCDPClient {
479479
if c.Upstream != nil {
480480
c.Upstream.Update(map[string]any{"upstream_cdp_send_timeout_ms": c.Config.ClientConfig.ClientCDPSendTimeoutMS})
481481
}
482-
if config.Upstream.UpstreamWSCDPURL != "" || config.Upstream.UpstreamWSConnectErrorSettleTimeoutMS != 0 || config.Upstream.UpstreamCDPSendTimeoutMS != 0 {
482+
if config.Upstream.UpstreamMode != "" || config.Upstream.UpstreamWSCDPURL != "" || config.Upstream.UpstreamWSConnectErrorSettleTimeoutMS != 0 || config.Upstream.UpstreamCDPSendTimeoutMS != 0 {
483+
if config.Upstream.UpstreamMode != "" {
484+
c.Config.Upstream.UpstreamMode = config.Upstream.UpstreamMode
485+
}
483486
if config.Upstream.UpstreamWSCDPURL != "" {
484487
c.Config.Upstream.UpstreamWSCDPURL = config.Upstream.UpstreamWSCDPURL
485488
}
@@ -738,7 +741,8 @@ func (c *ModCDPClient) ensureModCDPServerConfigured() error {
738741

739742
func (c *ModCDPClient) upstreamTransportConfig() map[string]any {
740743
return map[string]any{
741-
"upstream_ws_cdp_url": c.Config.Upstream.UpstreamWSCDPURL,
744+
"upstream_mode": c.Config.Upstream.UpstreamMode,
745+
"upstream_ws_cdp_url": c.Config.Upstream.UpstreamWSCDPURL,
742746
"upstream_ws_connect_error_settle_timeout_ms": c.Config.Upstream.UpstreamWSConnectErrorSettleTimeoutMS,
743747
"upstream_cdp_send_timeout_ms": c.Config.Upstream.UpstreamCDPSendTimeoutMS,
744748
}

go/modcdp/injector/ExtensionInjector.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ func (i *ExtensionInjector) Inject() (*ExtensionInjectionResult, error) {
184184
}
185185

186186
func (i ExtensionInjector) readyExpression() string {
187-
if i.Config.InjectorServiceWorkerReadyExpression == "" {
187+
if i.Config.InjectorServiceWorkerReadyExpression == "" || i.Config.InjectorServiceWorkerReadyExpression == modcdpReadyExpression {
188188
return modcdpReadyExpression
189189
}
190190
return fmt.Sprintf("(%s) && Boolean(%s)", modcdpReadyExpression, i.Config.InjectorServiceWorkerReadyExpression)

go/modcdp/injector/ExtensionInjector_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,20 @@ func TestExtensionInjectorBaseInjectReportsTheSubclassName(t *testing.T) {
4848
t.Fatalf("Inject error = %v", err)
4949
}
5050
}
51+
52+
func TestExtensionInjectorDoesNotWrapTheDefaultReadyExpressionTwice(t *testing.T) {
53+
injector := NewExtensionInjector(InjectorConfig{
54+
InjectorServiceWorkerReadyExpression: modcdpReadyExpression,
55+
})
56+
57+
if injector.readyExpression() != modcdpReadyExpression {
58+
t.Fatalf("readyExpression = %q", injector.readyExpression())
59+
}
60+
61+
injector.Update(InjectorConfig{
62+
InjectorServiceWorkerReadyExpression: "globalThis.ready === true",
63+
})
64+
if got := injector.readyExpression(); got != "("+modcdpReadyExpression+") && Boolean(globalThis.ready === true)" {
65+
t.Fatalf("custom readyExpression = %q", got)
66+
}
67+
}

go/modcdp/router/AutoSessionRouter.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
package router
66

77
import (
8+
"crypto/rand"
9+
"encoding/hex"
810
"fmt"
911
"strings"
1012
"sync"
@@ -176,23 +178,23 @@ func (r *AutoSessionRouter) Send(method string, params map[string]any, requested
176178
return r.upstream.Send(method, routedParams, sessionID)
177179
}
178180

179-
func (r *AutoSessionRouter) AttachToTarget(targetID string) string {
181+
func (r *AutoSessionRouter) AttachToTarget(targetID string) (string, error) {
180182
r.mu.Lock()
181183
sessionID := r.SessionId_from_targetId[targetID]
182184
r.mu.Unlock()
183185
if sessionID != "" {
184-
return sessionID
186+
return sessionID, nil
185187
}
186188
attachedSessionID, err := r.upstream.AttachToTarget(targetID)
187189
if err != nil {
188-
return ""
190+
return "", err
189191
}
190192
if attachedSessionID != "" {
191193
r.mu.Lock()
192194
r.recordTargetSession(targetID, attachedSessionID, r.Targets[targetID])
193195
r.mu.Unlock()
194196
}
195-
return attachedSessionID
197+
return attachedSessionID, nil
196198
}
197199

198200
func (r *AutoSessionRouter) EnsureSessionForTarget(targetID string) (string, error) {
@@ -233,7 +235,10 @@ func (r *AutoSessionRouter) EnsureRouteForTarget(targetID string) (string, strin
233235
return "", "", err
234236
}
235237
}
236-
sessionID := r.AttachToTarget(resolvedTargetID)
238+
sessionID, err := r.AttachToTarget(resolvedTargetID)
239+
if err != nil {
240+
return "", "", err
241+
}
237242
if sessionID == "" {
238243
r.recordTargetSessionlessAttachment(resolvedTargetID)
239244
return resolvedTargetID, "", nil
@@ -428,7 +433,7 @@ func (r *AutoSessionRouter) GetTopology(params map[string]any) (map[string]any,
428433
if params == nil {
429434
params = map[string]any{}
430435
}
431-
objectGroup := fmt.Sprintf("modcdp-topology-%d", time.Now().UnixMilli())
436+
objectGroup := fmt.Sprintf("modcdp-topology-%d-%s", time.Now().UnixMilli(), randomHexSuffix(8))
432437
targetResult, err := r.upstream.Send("Target.getTargets", map[string]any{}, "")
433438
if err != nil {
434439
return nil, err
@@ -1079,6 +1084,14 @@ func cloneStringMap(input map[string]string) map[string]string {
10791084
return output
10801085
}
10811086

1087+
func randomHexSuffix(bytesLen int) string {
1088+
buf := make([]byte, bytesLen)
1089+
if _, err := rand.Read(buf); err == nil {
1090+
return hex.EncodeToString(buf)
1091+
}
1092+
return fmt.Sprintf("%d", time.Now().UnixNano())
1093+
}
1094+
10821095
func intFromAny(value any) (int, bool) {
10831096
switch typed := value.(type) {
10841097
case int:

go/modcdp/router/AutoSessionRouter_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,25 @@ func TestAutoSessionRouterTracksRealTargetSessionsAndExecutionContextsFromLiveCD
101101
if !foundContext {
102102
t.Fatalf("context id %d for session %s was not recorded", contextID, sessionID)
103103
}
104+
topologyOne, err := cdp.Router.GetTopology(nil)
105+
if err != nil {
106+
t.Fatal(err)
107+
}
108+
objectGroupOne, _ := topologyOne["objectGroup"].(string)
109+
if !regexp.MustCompile(`^modcdp-topology-\d+-[0-9a-f]+$`).MatchString(objectGroupOne) {
110+
t.Fatalf("topology objectGroup = %q", objectGroupOne)
111+
}
112+
topologyTwo, err := cdp.Router.GetTopology(nil)
113+
if err != nil {
114+
t.Fatal(err)
115+
}
116+
objectGroupTwo, _ := topologyTwo["objectGroup"].(string)
117+
if objectGroupOne == objectGroupTwo {
118+
t.Fatalf("topology objectGroup was reused: %q", objectGroupOne)
119+
}
120+
if _, _, err := cdp.Router.EnsureRouteForTarget("missing-target-id"); err == nil {
121+
t.Fatal("EnsureRouteForTarget should return the attach error for an unknown target")
122+
}
104123

105124
detachTarget(t, cdp, sessionID)
106125
expectEventually(t, func() error {

go/modcdp/transport/UpstreamTransport.go

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package transport
66

77
import (
8+
"encoding/json"
89
"fmt"
910
"net"
1011
"net/url"
@@ -126,15 +127,33 @@ func (e *UpstreamTransport) Close() error {
126127
return nil
127128
}
128129

129-
func (e *UpstreamTransport) Send(command string, params map[string]any, sessionID string, timeout ...time.Duration) (map[string]any, error) {
130+
func (e *UpstreamTransport) Send(command any, params map[string]any, sessionID string, timeout ...time.Duration) (map[string]any, error) {
131+
method, ok := command.(string)
132+
if !ok {
133+
message, ok := command.(types.CdpCommandMessage)
134+
if !ok {
135+
return nil, fmt.Errorf("command must be a CDP method name or CdpCommandMessage")
136+
}
137+
payload := map[string]any{
138+
"id": message.ID,
139+
"method": message.Method,
140+
}
141+
if message.Params != nil {
142+
payload["params"] = message.Params
143+
}
144+
if message.SessionID != "" {
145+
payload["sessionId"] = message.SessionID
146+
}
147+
return map[string]any{}, e.writeCommand(payload)
148+
}
130149
e.pendingMu.Lock()
131150
e.nextID++
132151
id := e.nextID
133152
done := make(chan map[string]any, 1)
134153
e.pending[id] = done
135154
e.pendingMu.Unlock()
136155

137-
message := map[string]any{"id": id, "method": command, "params": params}
156+
message := map[string]any{"id": id, "method": method, "params": params}
138157
if sessionID != "" {
139158
message["sessionId"] = sessionID
140159
}
@@ -151,7 +170,7 @@ func (e *UpstreamTransport) Send(command string, params map[string]any, sessionI
151170
if effectiveTimeout <= 0 {
152171
response := <-done
153172
if errObj, ok := response["error"].(map[string]any); ok {
154-
return nil, fmt.Errorf("%s failed: %v", command, errObj["message"])
173+
return nil, fmt.Errorf("%s failed: %v", method, errObj["message"])
155174
}
156175
if result, ok := response["result"].(map[string]any); ok {
157176
return result, nil
@@ -163,10 +182,10 @@ func (e *UpstreamTransport) Send(command string, params map[string]any, sessionI
163182
e.pendingMu.Lock()
164183
delete(e.pending, id)
165184
e.pendingMu.Unlock()
166-
return nil, fmt.Errorf("%s timed out after %s", command, effectiveTimeout)
185+
return nil, fmt.Errorf("%s timed out after %s", method, effectiveTimeout)
167186
case response := <-done:
168187
if errObj, ok := response["error"].(map[string]any); ok {
169-
return nil, fmt.Errorf("%s failed: %v", command, errObj["message"])
188+
return nil, fmt.Errorf("%s failed: %v", method, errObj["message"])
170189
}
171190
if result, ok := response["result"].(map[string]any); ok {
172191
return result, nil
@@ -332,6 +351,50 @@ func (e *UpstreamTransport) EmitRecv(message map[string]any) {
332351
e.emitRecv(message)
333352
}
334353

354+
func (e *UpstreamTransport) parseAndEmitRecv(data []byte) error {
355+
var message map[string]any
356+
if err := json.Unmarshal(data, &message); err != nil {
357+
return fmt.Errorf("invalid CDP message: %w", err)
358+
}
359+
if _, ok := commandID(message["id"]); ok {
360+
if errObj, hasError := message["error"]; hasError && errObj != nil {
361+
errorMap, ok := errObj.(map[string]any)
362+
if !ok {
363+
return fmt.Errorf("invalid CDP response error")
364+
}
365+
if message, ok := errorMap["message"].(string); !ok || message == "" {
366+
return fmt.Errorf("invalid CDP response error message")
367+
}
368+
}
369+
if sessionID, ok := message["sessionId"]; ok && sessionID != nil {
370+
if _, ok := sessionID.(string); !ok {
371+
return fmt.Errorf("invalid CDP response sessionId")
372+
}
373+
}
374+
e.emitRecv(message)
375+
return nil
376+
}
377+
if _, hasID := message["id"]; hasID {
378+
return fmt.Errorf("invalid CDP response id")
379+
}
380+
method, _ := message["method"].(string)
381+
if method == "" {
382+
return fmt.Errorf("invalid CDP event method")
383+
}
384+
if params, ok := message["params"]; ok && params != nil {
385+
if _, ok := params.(map[string]any); !ok {
386+
return fmt.Errorf("invalid CDP event params")
387+
}
388+
}
389+
if sessionID, ok := message["sessionId"]; ok && sessionID != nil {
390+
if _, ok := sessionID.(string); !ok {
391+
return fmt.Errorf("invalid CDP event sessionId")
392+
}
393+
}
394+
e.emitRecv(message)
395+
return nil
396+
}
397+
335398
func (e *UpstreamTransport) emitUpstreamEvent(method string, payload map[string]any, targetID string, sessionID string) {
336399
e.listenerMu.Lock()
337400
listeners := append([]upstreamEventListener(nil), e.eventListeners[method]...)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// MODCDP_TRANSLATE_TEST: GO-SPECIFIC INTERNAL COVERAGE.
2+
// The public translated tests cover behavior through user-facing transport APIs.
3+
package transport
4+
5+
import (
6+
"strings"
7+
"testing"
8+
)
9+
10+
func TestUpstreamTransportRejectsInvalidIncomingMessages(t *testing.T) {
11+
transport := NewUpstreamTransport(UpstreamTransportConfig{})
12+
13+
for _, testCase := range []struct {
14+
name string
15+
message string
16+
want string
17+
}{
18+
{name: "json", message: "{", want: "invalid CDP message"},
19+
{name: "response id", message: `{"id":"one","result":{}}`, want: "invalid CDP response id"},
20+
{name: "event method", message: `{"params":{}}`, want: "invalid CDP event method"},
21+
{name: "event params", message: `{"method":"Runtime.executionContextCreated","params":[]}`, want: "invalid CDP event params"},
22+
} {
23+
t.Run(testCase.name, func(t *testing.T) {
24+
err := transport.parseAndEmitRecv([]byte(testCase.message))
25+
if err == nil || !strings.Contains(err.Error(), testCase.want) {
26+
t.Fatalf("parse error = %v", err)
27+
}
28+
})
29+
}
30+
}

go/modcdp/transport/UpstreamTransport_test.go

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,22 @@
44
// - ./python/tests/test_UpstreamTransport.py
55
// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED.
66
// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing.
7-
package transport_test
7+
package transport
88

99
import (
10-
. "github.com/browserbase/modcdp/go/modcdp/transport"
1110
"reflect"
1211
"strings"
1312
"testing"
13+
14+
"github.com/browserbase/modcdp/go/modcdp/types"
1415
)
1516

1617
type testUpstreamTransport struct {
1718
UpstreamTransport
1819
}
1920

20-
func (t *testUpstreamTransport) emit(message map[string]any) {
21-
t.EmitRecv(message)
21+
func (t *testUpstreamTransport) emit(message string) error {
22+
return t.parseAndEmitRecv([]byte(message))
2223
}
2324

2425
func TestOwnsSharedTransportConfigAndRecvCallbacks(t *testing.T) {
@@ -41,14 +42,20 @@ func TestOwnsSharedTransportConfigAndRecvCallbacks(t *testing.T) {
4142
testTransport := &testUpstreamTransport{UpstreamTransport: NewUpstreamTransport(UpstreamTransportConfig{})}
4243
parsed := []map[string]any{}
4344
testTransport.OnRecv(func(message map[string]any) { parsed = append(parsed, message) })
44-
testTransport.emit(map[string]any{"id": 1, "result": map[string]any{"ok": true}})
45-
testTransport.emit(map[string]any{"id": 2, "result": true})
46-
testTransport.emit(map[string]any{"id": 3, "result": 0})
47-
testTransport.emit(map[string]any{"method": "Runtime.executionContextCreated", "params": map[string]any{}})
45+
for _, message := range []string{
46+
`{"id":1,"result":{"ok":true}}`,
47+
`{"id":2,"result":true}`,
48+
`{"id":3,"result":0}`,
49+
`{"method":"Runtime.executionContextCreated","params":{}}`,
50+
} {
51+
if err := testTransport.emit(message); err != nil {
52+
t.Fatal(err)
53+
}
54+
}
4855
expected := []map[string]any{
49-
{"id": 1, "result": map[string]any{"ok": true}},
50-
{"id": 2, "result": true},
51-
{"id": 3, "result": 0},
56+
{"id": float64(1), "result": map[string]any{"ok": true}},
57+
{"id": float64(2), "result": true},
58+
{"id": float64(3), "result": float64(0)},
5259
{"method": "Runtime.executionContextCreated", "params": map[string]any{}},
5360
}
5461
if !reflect.DeepEqual(parsed, expected) {
@@ -62,7 +69,7 @@ func TestOwnsSharedTransportConfigAndRecvCallbacks(t *testing.T) {
6269
if err := transport.Connect(); err == nil || !strings.Contains(err.Error(), "Connect is not implemented") {
6370
t.Fatalf("connect error = %v", err)
6471
}
65-
if _, err := transport.Send("Browser.getVersion", map[string]any{}, ""); err == nil || !strings.Contains(err.Error(), "send is not implemented") {
72+
if _, err := transport.Send(types.CdpCommandMessage{ID: 1, Method: "Browser.getVersion", Params: map[string]any{}}, nil, ""); err == nil || !strings.Contains(err.Error(), "send is not implemented") {
6673
t.Fatalf("send error = %v", err)
6774
}
6875
}

0 commit comments

Comments
 (0)