diff --git a/js/modules/k6/ws/ws.go b/js/modules/k6/ws/ws.go index 8a54be1fb75f..30a2e2e08d45 100644 --- a/js/modules/k6/ws/ws.go +++ b/js/modules/k6/ws/ws.go @@ -292,7 +292,8 @@ func (*WS) Connect(ctx context.Context, url string, args ...goja.Value) (*WSHTTP Tags: socket.sampleTags, Value: 1, }) - socket.handleEvent("message", rt.ToValue(string(readData))) + ab := rt.NewArrayBuffer(readData) + socket.handleEvent("message", rt.ToValue(string(readData)), rt.ToValue(&ab)) case readErr := <-readErrChan: socket.handleEvent("error", rt.ToValue(readErr)) @@ -334,13 +335,27 @@ func (s *Socket) handleEvent(event string, args ...goja.Value) { } } -func (s *Socket) Send(message string) { - // NOTE: No binary message support for the time being since goja doesn't - // support typed arrays. +// Send writes the given string or ArrayBuffer message to the connection. +func (s *Socket) Send(message interface{}) { rt := common.GetRuntime(s.ctx) - writeData := []byte(message) - if err := s.conn.WriteMessage(websocket.TextMessage, writeData); err != nil { + var ( + msgType int + msg []byte + ) + switch m := message.(type) { + case string: + msgType = websocket.TextMessage + msg = []byte(m) + case goja.ArrayBuffer: + msgType = websocket.BinaryMessage + msg = m.Bytes() + default: + err := fmt.Errorf("unsupported message type: %T, expected string or ArrayBuffer", message) + common.Throw(common.GetRuntime(s.ctx), err) + } + + if err := s.conn.WriteMessage(msgType, msg); err != nil { s.handleEvent("error", rt.ToValue(err)) } diff --git a/js/modules/k6/ws/ws_test.go b/js/modules/k6/ws/ws_test.go index 90d2c3ae5cd4..5c26bab27175 100644 --- a/js/modules/k6/ws/ws_test.go +++ b/js/modules/k6/ws/ws_test.go @@ -175,6 +175,18 @@ func TestSession(t *testing.T) { assert.NoError(t, err) }) + t.Run("send_err", func(t *testing.T) { + _, err := rt.RunString(sr(` + var res = ws.connect("WSBIN_URL/ws-echo", function(socket){ + socket.on("open", function() { + socket.send(1); + }) + }); + `)) + require.Error(t, err) + require.Contains(t, err.Error(), "unsupported message type: int64, expected string or ArrayBuffer") + }) + samplesBuf := stats.GetBufferedSamples(samples) assertSessionMetricsEmitted(t, samplesBuf, "", sr("WSBIN_URL/ws-echo"), 101, "") assertMetricEmitted(t, metrics.WSMessagesSent, samplesBuf, sr("WSBIN_URL/ws-echo")) @@ -347,6 +359,62 @@ func TestSession(t *testing.T) { } } +func TestSocketSendBinary(t *testing.T) { + t.Parallel() + tb := httpmultibin.NewHTTPMultiBin(t) + defer tb.Cleanup() + sr := tb.Replacer.Replace + + root, err := lib.NewGroup("", nil) + assert.NoError(t, err) + + rt := goja.New() + rt.SetFieldNameMapper(common.FieldNameMapper{}) + samples := make(chan stats.SampleContainer, 1000) + state := &lib.State{ + Group: root, + Dialer: tb.Dialer, + Options: lib.Options{ + SystemTags: stats.NewSystemTagSet( + stats.TagURL, + stats.TagProto, + stats.TagStatus, + stats.TagSubproto, + ), + }, + Samples: samples, + TLSConfig: tb.TLSClientConfig, + } + + ctx := context.Background() + ctx = lib.WithState(ctx, state) + ctx = common.WithRuntime(ctx, rt) + + err = rt.Set("ws", common.Bind(rt, New(), &ctx)) + assert.NoError(t, err) + + _, err = rt.RunString(sr(` + var res = ws.connect('WSBIN_URL/ws-echo', function(socket){ + var data = new Uint8Array([104, 101, 108, 108, 111]); // 'hello' + socket.on('open', function() { + socket.send(data.buffer); + }) + socket.on('message', function (msg, msgBin){ + if (msg !== 'hello') { + throw new Error('received unexpected message: ' + msg); + } + let decText = String.fromCharCode.apply(null, new Uint8Array(msgBin)); + decText = decodeURIComponent(escape(decText)); + if (decText !== 'hello') { + throw new Error('received unexpected binary message: ' + decText); + } + socket.close() + }); + }); + `)) + assert.NoError(t, err) +} + func TestErrors(t *testing.T) { t.Parallel() tb := httpmultibin.NewHTTPMultiBin(t)