Unverified Commit e6e4a489 authored by Simon Esposito's avatar Simon Esposito Committed by GitHub
Browse files

Correctly handle binary data in js runtime matches (#733)

Do not convert data to a string when passing the messages to the runtime,
this can cause encoding issues in the goja vm if the content is binary data.
Pass it as a JS TypedArray (Uint8Array) instead.

Introduce js runtime helper functions binaryToString and stringToBinary to
facilitate message data conversion.

Resolves #659
parent feb2de08
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr
## [Unreleased]
### Added
- Add ctx field to access http request headers in the runtimes.
- New JS runtime stringToBinary and binaryToString functions.

### Fixed
- Gracefully close Lua matches when call queue fills up.
@@ -15,6 +16,7 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr

### Changed
- Set JS runtime custom error message as the returned payload message in RPC requests.
- JS runtime match data changed to Uint8Array.

## [3.9.0] - 2021-10-29
### Added
+11 −7
Original line number Diff line number Diff line
@@ -393,10 +393,10 @@ func (rm *RuntimeJavaScriptMatchCore) MatchLoop(tick int64, state interface{}, i
		msgMap := make(map[string]interface{}, 5)
		msgMap["sender"] = presenceMap
		msgMap["opCode"] = msg.OpCode
		if msg.Data != nil {
			msgMap["data"] = string(msg.Data)
		} else {
		if msg.Data == nil {
			msgMap["data"] = goja.Null()
		} else {
			msgMap["data"] = rm.vm.NewArrayBuffer(msg.Data)
		}
		msgMap["reliable"] = msg.Reliable
		msgMap["receiveTimeMs"] = msg.ReceiveTime
@@ -563,11 +563,15 @@ func (rm *RuntimeJavaScriptMatchCore) validateBroadcast(r *goja.Runtime, f goja.
	var dataBytes []byte
	data := f.Argument(1)
	if !goja.IsUndefined(data) && !goja.IsNull(data) {
		dataStr, ok := data.Export().(string)
		if !ok {
			panic(r.NewTypeError("expects data to be a string or nil"))
		dataExport := data.Export()
		switch dataExport.(type) {
		case string:
			dataBytes = []byte(dataExport.(string))
		case goja.ArrayBuffer:
			dataBytes = dataExport.(goja.ArrayBuffer).Bytes()
		default:
			panic(r.NewTypeError("expects data to be an Uint8Array, a string or nil"))
		}
		dataBytes = []byte(dataStr)
	}

	filter := f.Argument(2)
+37 −0
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import (
	"net/http"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/dop251/goja"
	"github.com/gofrs/uuid"
@@ -250,6 +251,42 @@ func (n *runtimeJavascriptNakamaModule) mappings(r *goja.Runtime) map[string]fun
		"channelMessageSend":              n.channelMessageSend(r),
		"channelMessageUpdate":            n.channelMessageUpdate(r),
		"channeldIdBuild":                 n.channelIdBuild(r),
		"binaryToString":                  n.binaryToString(r),
		"stringToBinary":                  n.stringToBinary(r),
	}
}

func (n *runtimeJavascriptNakamaModule) binaryToString(r *goja.Runtime) func(goja.FunctionCall) goja.Value {
	return func(f goja.FunctionCall) goja.Value {
		if goja.IsUndefined(f.Argument(0)) || goja.IsNull(f.Argument(0)) {
			panic(r.NewTypeError("expects a Uint8Array object"))
		}

		data, ok := f.Argument(0).Export().(goja.ArrayBuffer)
		if !ok {
			panic(r.NewTypeError("expects a Uint8Array object"))
		}

		if !utf8.Valid(data.Bytes()) {
			panic(r.NewTypeError("expects data to be UTF-8 encoded"))
		}

		return r.ToValue(string(data.Bytes()))
	}
}

func (n *runtimeJavascriptNakamaModule) stringToBinary(r *goja.Runtime) func(goja.FunctionCall) goja.Value {
	return func(f goja.FunctionCall) goja.Value {
		if goja.IsUndefined(f.Argument(0)) || goja.IsNull(f.Argument(0)) {
			panic(r.NewTypeError("expects a string"))
		}

		str, ok := f.Argument(0).Export().(string)
		if !ok {
			panic(r.NewTypeError("expects a string"))
		}

		return r.ToValue([]byte(str))
	}
}