Loading CHANGELOG.md +10 −0 Original line number Diff line number Diff line Loading @@ -4,6 +4,16 @@ All notable changes to this project are documented below. The format is based on [keep a changelog](http://keepachangelog.com/) and this project uses [semantic versioning](http://semver.org/). ## [Unreleased] ### Added - New script runtime functions to convert UUIDs between byte and string representations. ### Changed - Improve index selection in storage list operations. - Payloads in `register_before` hooks now use `PascalCase` field names and expose correctly formatted IDs. - Metadata regions in users, groups, and leaderboard records are now exposed to the script runtime as Lua tables. ### Fixed - Script runtime batch user update operations now process correctly. ## [1.0.0] - 2017-08-01 ### Added Loading server/core_runtime_hooks.go +2 −27 Original line number Diff line number Diff line Loading @@ -31,16 +31,6 @@ func RuntimeBeforeHook(runtime *Runtime, jsonpbMarshaler *jsonpb.Marshaler, json return envelope, nil } strEnvelope, err := jsonpbMarshaler.MarshalToString(envelope) if err != nil { return nil, err } var jsonEnvelope map[string]interface{} if err = json.Unmarshal([]byte(strEnvelope), &jsonEnvelope); err != nil { return nil, err } userId := uuid.Nil handle := "" expiry := int64(0) Loading @@ -50,22 +40,7 @@ func RuntimeBeforeHook(runtime *Runtime, jsonpbMarshaler *jsonpb.Marshaler, json expiry = session.expiry } result, fnErr := runtime.InvokeFunctionBefore(fn, userId, handle, expiry, jsonEnvelope) if fnErr != nil { return nil, fnErr } bytesEnvelope, err := json.Marshal(result) if err != nil { return nil, err } resultEnvelope := &Envelope{} if err = jsonpbUnmarshaler.Unmarshal(bytes.NewReader(bytesEnvelope), resultEnvelope); err != nil { return nil, err } return resultEnvelope, nil return runtime.InvokeFunctionBefore(fn, userId, handle, expiry, jsonpbMarshaler, jsonpbUnmarshaler, envelope) } func RuntimeAfterHook(logger *zap.Logger, runtime *Runtime, jsonpbMarshaler *jsonpb.Marshaler, messageType string, envelope *Envelope, session *session) { Loading Loading @@ -121,7 +96,7 @@ func RuntimeBeforeHookAuthentication(runtime *Runtime, jsonpbMarshaler *jsonpb.M handle := "" expiry := int64(0) result, fnErr := runtime.InvokeFunctionBefore(fn, userId, handle, expiry, jsonEnvelope) result, fnErr := runtime.InvokeFunctionBeforeAuthentication(fn, userId, handle, expiry, jsonEnvelope) if fnErr != nil { return nil, fnErr } Loading server/core_storage.go +6 −6 Original line number Diff line number Diff line Loading @@ -105,6 +105,12 @@ func StorageList(logger *zap.Logger, db *sql.DB, caller uuid.UUID, userID []byte // Select the correct index. NOTE: should be removed when DB index selection is smarter. index := "" if len(userID) == 0 { if collection == "" { index = "deleted_at_bucket_read_collection_record_user_id_idx" } else { index = "deleted_at_bucket_collection_read_record_user_id_idx" } } else { if bucket == "" { index = "deleted_at_user_id_read_bucket_collection_record_idx" } else if collection == "" { Loading @@ -112,12 +118,6 @@ func StorageList(logger *zap.Logger, db *sql.DB, caller uuid.UUID, userID []byte } else { index = "deleted_at_user_id_bucket_collection_read_record_idx" } } else { if collection == "" { index = "deleted_at_bucket_read_collection_record_user_id_idx" } else { index = "deleted_at_bucket_collection_read_record_user_id_idx" } } // Set up the query. Loading server/runtime.go +315 −2 Original line number Diff line number Diff line Loading @@ -24,6 +24,9 @@ import ( "database/sql" "bytes" "encoding/json" "github.com/gogo/protobuf/jsonpb" "github.com/satori/go.uuid" "github.com/yuin/gopher-lua" "go.uber.org/zap" Loading Loading @@ -222,7 +225,35 @@ func (r *Runtime) InvokeFunctionRPC(fn *lua.LFunction, uid uuid.UUID, handle str return nil, errors.New("Runtime function returned invalid data. Only allowed one return value of type String/Byte") } func (r *Runtime) InvokeFunctionBefore(fn *lua.LFunction, uid uuid.UUID, handle string, sessionExpiry int64, payload map[string]interface{}) (map[string]interface{}, error) { func (r *Runtime) InvokeFunctionBefore(fn *lua.LFunction, uid uuid.UUID, handle string, sessionExpiry int64, jsonpbMarshaler *jsonpb.Marshaler, jsonpbUnmarshaler *jsonpb.Unmarshaler, envelope *Envelope) (*Envelope, error) { l, _ := r.NewStateThread() defer l.Close() ctx := NewLuaContext(l, r.luaEnv, BEFORE, uid, handle, sessionExpiry) var lv lua.LValue var err error if envelope != nil { lv, err = ConvertEnvelopeToLTable(l, jsonpbMarshaler, envelope) if err != nil { return nil, err } } retValue, err := r.invokeFunction(l, fn, ctx, lv) if err != nil { return nil, err } if retValue == nil || retValue == lua.LNil { return nil, errors.New("Runtime before hook did not return the payload") } else if retValue.Type() == lua.LTTable { return ConvertLTableToEnvelope(l, jsonpbUnmarshaler, retValue.(*lua.LTable), envelope) } return nil, errors.New("Runtime function returned invalid data. Only allowed one return value of type Table") } func (r *Runtime) InvokeFunctionBeforeAuthentication(fn *lua.LFunction, uid uuid.UUID, handle string, sessionExpiry int64, payload map[string]interface{}) (map[string]interface{}, error) { l, _ := r.NewStateThread() defer l.Close() Loading @@ -238,7 +269,7 @@ func (r *Runtime) InvokeFunctionBefore(fn *lua.LFunction, uid uuid.UUID, handle } if retValue == nil || retValue == lua.LNil { return nil, nil return nil, errors.New("Runtime before hook did not return the payload") } else if retValue.Type() == lua.LTTable { return ConvertLuaTable(retValue.(*lua.LTable)), nil } Loading Loading @@ -312,3 +343,285 @@ func (r *Runtime) invokeFunction(l *lua.LState, fn *lua.LFunction, ctx *lua.LTab func (r *Runtime) Stop() { r.vm.Close() } func ConvertEnvelopeToLTable(l *lua.LState, jsonpbMarshaler *jsonpb.Marshaler, envelope *Envelope) (lua.LValue, error) { lt := l.NewTable() switch envelope.Payload.(type) { case *Envelope_GroupsRemove: ids := l.NewTable() for i, l := range envelope.GetGroupsRemove().GroupIds { gid, err := uuid.FromBytes(l) if err != nil { return nil, errors.New("Invalid Group ID in GroupsRemove conversion to script runtime") } ids.RawSetInt(i+1, lua.LString(gid.String())) } groupIDs := l.NewTable() groupIDs.RawSetString("GroupIds", ids) lt.RawSetString("GroupsRemove", groupIDs) case *Envelope_GroupsJoin: ids := l.NewTable() for i, l := range envelope.GetGroupsJoin().GroupIds { gid, err := uuid.FromBytes(l) if err != nil { return nil, errors.New("Invalid Group ID in GroupsJoin conversion to script runtime") } ids.RawSetInt(i+1, lua.LString(gid.String())) } groupIDs := l.NewTable() groupIDs.RawSetString("GroupIds", ids) lt.RawSetString("GroupsJoin", groupIDs) case *Envelope_GroupsLeave: ids := l.NewTable() for i, l := range envelope.GetGroupsLeave().GroupIds { gid, err := uuid.FromBytes(l) if err != nil { return nil, errors.New("Invalid Group ID in GroupsLeave conversion to script runtime") } ids.RawSetInt(i+1, lua.LString(gid.String())) } groupIDs := l.NewTable() groupIDs.RawSetString("GroupIds", ids) lt.RawSetString("GroupsLeave", groupIDs) case *Envelope_GroupUsersKick: pairs := l.NewTable() for i, p := range envelope.GetGroupUsersKick().GroupUsers { gid, err := uuid.FromBytes(p.GroupId) if err != nil { return nil, errors.New("Invalid Group ID in GroupUsersKick conversion to script runtime") } uid, err := uuid.FromBytes(p.UserId) if err != nil { return nil, errors.New("Invalid User ID in GroupUsersKick conversion to script runtime") } pair := l.NewTable() pair.RawSetString("GroupId", lua.LString(gid.String())) pair.RawSetString("UserId", lua.LString(uid.String())) pairs.RawSetInt(i+1, pair) } kicks := l.NewTable() kicks.RawSetString("GroupUsers", pairs) lt.RawSetString("GroupUsersKick", kicks) case *Envelope_GroupUsersPromote: pairs := l.NewTable() for i, p := range envelope.GetGroupUsersPromote().GroupUsers { gid, err := uuid.FromBytes(p.GroupId) if err != nil { return nil, errors.New("Invalid Group ID in GroupUsersPromote conversion to script runtime") } uid, err := uuid.FromBytes(p.UserId) if err != nil { return nil, errors.New("Invalid User ID in GroupUsersPromote conversion to script runtime") } pair := l.NewTable() pair.RawSetString("GroupId", lua.LString(gid.String())) pair.RawSetString("UserId", lua.LString(uid.String())) pairs.RawSetInt(i+1, pair) } kicks := l.NewTable() kicks.RawSetString("GroupUsers", pairs) lt.RawSetString("GroupUsersPromote", kicks) default: strEnvelope, err := jsonpbMarshaler.MarshalToString(envelope) if err != nil { return nil, err } var jsonEnvelope map[string]interface{} if err = json.Unmarshal([]byte(strEnvelope), &jsonEnvelope); err != nil { return nil, err } for k, v := range jsonEnvelope { lt.RawSetString(k, convertValue(l, v)) } } return lt, nil } func ConvertLTableToEnvelope(l *lua.LState, jsonpbUnmarshaler *jsonpb.Unmarshaler, lt *lua.LTable, envelope *Envelope) (*Envelope, error) { switch envelope.Payload.(type) { case *Envelope_GroupsRemove: groupsRemove := lt.RawGetString("GroupsRemove") if groupsRemove.Type() != lua.LTTable { return nil, errors.New("Invalid payload in GroupsRemove conversion from script runtime") } groupIds := groupsRemove.(*lua.LTable).RawGetString("GroupIds") if groupIds.Type() != lua.LTTable { return nil, errors.New("Invalid payload in GroupsRemove conversion from script runtime") } ids := make([][]byte, 0) var err error groupIds.(*lua.LTable).ForEach(func(k lua.LValue, v lua.LValue) { if v.Type() != lua.LTString { err = errors.New("Invalid Group ID in GroupsRemove conversion from script runtime") return } gid, e := uuid.FromString(v.String()) if e != nil { err = errors.New("Invalid Group ID in GroupsRemove conversion from script runtime") return } ids = append(ids, gid.Bytes()) }) if err != nil { return nil, err } envelope.GetGroupsRemove().GroupIds = ids case *Envelope_GroupsJoin: groupsJoin := lt.RawGetString("GroupsJoin") if groupsJoin.Type() != lua.LTTable { return nil, errors.New("Invalid payload in GroupsJoin conversion from script runtime") } groupIds := groupsJoin.(*lua.LTable).RawGetString("GroupIds") if groupIds.Type() != lua.LTTable { return nil, errors.New("Invalid payload in GroupsJoin conversion from script runtime") } ids := make([][]byte, 0) var err error groupIds.(*lua.LTable).ForEach(func(k lua.LValue, v lua.LValue) { if v.Type() != lua.LTString { err = errors.New("Invalid Group ID in GroupsJoin conversion from script runtime") return } gid, e := uuid.FromString(v.String()) if e != nil { err = errors.New("Invalid Group ID in GroupsJoin conversion from script runtime") return } ids = append(ids, gid.Bytes()) }) if err != nil { return nil, err } envelope.GetGroupsJoin().GroupIds = ids case *Envelope_GroupsLeave: groupsLeave := lt.RawGetString("GroupsLeave") if groupsLeave.Type() != lua.LTTable { return nil, errors.New("Invalid payload in GroupsLeave conversion from script runtime") } groupIds := groupsLeave.(*lua.LTable).RawGetString("GroupIds") if groupIds.Type() != lua.LTTable { return nil, errors.New("Invalid payload in GroupsLeave conversion from script runtime") } ids := make([][]byte, 0) var err error groupIds.(*lua.LTable).ForEach(func(k lua.LValue, v lua.LValue) { if v.Type() != lua.LTString { err = errors.New("Invalid Group ID in GroupsLeave conversion from script runtime") return } gid, e := uuid.FromString(v.String()) if e != nil { err = errors.New("Invalid Group ID in GroupsLeave conversion from script runtime") return } ids = append(ids, gid.Bytes()) }) if err != nil { return nil, err } envelope.GetGroupsLeave().GroupIds = ids case *Envelope_GroupUsersKick: groupUsersKick := lt.RawGetString("GroupUsersKick") if groupUsersKick.Type() != lua.LTTable { return nil, errors.New("Invalid payload in GroupUsersKick conversion from script runtime") } groupUsers := groupUsersKick.(*lua.LTable).RawGetString("GroupUsers") if groupUsers.Type() != lua.LTTable { return nil, errors.New("Invalid payload in GroupUsersKick conversion from script runtime") } gu := make([]*TGroupUsersKick_GroupUserKick, 0) var err error groupUsers.(*lua.LTable).ForEach(func(k lua.LValue, v lua.LValue) { if v.Type() != lua.LTTable { err = errors.New("Invalid Group User pair in GroupUsersKick conversion from script runtime") return } vt := v.(*lua.LTable) g := vt.RawGetString("GroupId") if g.Type() != lua.LTString { err = errors.New("Invalid Group ID in GroupUsersKick conversion from script runtime") return } gid, e := uuid.FromString(g.String()) if e != nil { err = errors.New("Invalid Group ID in GroupUsersKick conversion from script runtime") return } u := vt.RawGetString("UserId") if u.Type() != lua.LTString { err = errors.New("Invalid User ID in GroupUsersKick conversion from script runtime") return } uid, e := uuid.FromString(u.String()) if e != nil { err = errors.New("Invalid User ID in GroupUsersKick conversion from script runtime") return } gu = append(gu, &TGroupUsersKick_GroupUserKick{GroupId: gid.Bytes(), UserId: uid.Bytes()}) }) if err != nil { return nil, err } envelope.GetGroupUsersKick().GroupUsers = gu case *Envelope_GroupUsersPromote: groupUsersPromote := lt.RawGetString("GroupUsersPromote") if groupUsersPromote.Type() != lua.LTTable { return nil, errors.New("Invalid payload in GroupUsersPromote conversion from script runtime") } groupUsers := groupUsersPromote.(*lua.LTable).RawGetString("GroupUsers") if groupUsers.Type() != lua.LTTable { return nil, errors.New("Invalid payload in GroupUsersPromote conversion from script runtime") } gu := make([]*TGroupUsersPromote_GroupUserPromote, 0) var err error groupUsers.(*lua.LTable).ForEach(func(k lua.LValue, v lua.LValue) { if v.Type() != lua.LTTable { err = errors.New("Invalid Group User pair in GroupUsersPromote conversion from script runtime") return } vt := v.(*lua.LTable) g := vt.RawGetString("GroupId") if g.Type() != lua.LTString { err = errors.New("Invalid Group ID in GroupUsersPromote conversion from script runtime") return } gid, e := uuid.FromString(g.String()) if e != nil { err = errors.New("Invalid Group ID in GroupUsersPromote conversion from script runtime") return } u := vt.RawGetString("UserId") if u.Type() != lua.LTString { err = errors.New("Invalid User ID in GroupUsersPromote conversion from script runtime") return } uid, e := uuid.FromString(u.String()) if e != nil { err = errors.New("Invalid User ID in GroupUsersPromote conversion from script runtime") return } gu = append(gu, &TGroupUsersPromote_GroupUserPromote{GroupId: gid.Bytes(), UserId: uid.Bytes()}) }) if err != nil { return nil, err } envelope.GetGroupUsersPromote().GroupUsers = gu default: result := ConvertLuaTable(lt) bytesEnvelope, err := json.Marshal(result) if err != nil { return nil, err } if err = jsonpbUnmarshaler.Unmarshal(bytes.NewReader(bytesEnvelope), envelope); err != nil { return nil, err } } return envelope, nil } server/runtime_nakama_module.go +120 −24 Original line number Diff line number Diff line Loading @@ -77,6 +77,8 @@ func NewNakamaModule(logger *zap.Logger, db *sql.DB, l *lua.LState, notification func (n *NakamaModule) Loader(l *lua.LState) int { mod := l.SetFuncs(l.NewTable(), map[string]lua.LGFunction{ "uuid_v4": n.uuidV4, "uuid_bytes_to_string": n.uuidBytesToString, "uuid_string_to_bytes": n.uuidStringToBytes, "http_request": n.httpRequest, "json_encode": n.jsonEncode, "json_decode": n.jsonDecode, Loading Loading @@ -122,6 +124,36 @@ func (n *NakamaModule) uuidV4(l *lua.LState) int { return 1 } func (n *NakamaModule) uuidBytesToString(l *lua.LState) int { uuidBytes := l.CheckString(1) if uuidBytes == "" { l.ArgError(1, "Expects a UUID byte string") return 0 } u, err := uuid.FromBytes([]byte(uuidBytes)) if err != nil { l.ArgError(1, "Not a valid UUID byte string") return 0 } l.Push(lua.LString(u.String())) return 1 } func (n *NakamaModule) uuidStringToBytes(l *lua.LState) int { uuidString := l.CheckString(1) if uuidString == "" { l.ArgError(1, "Expects a UUID string") return 0 } u, err := uuid.FromString(uuidString) if err != nil { l.ArgError(1, "Not a valid UUID string") return 0 } l.Push(lua.LString(u.Bytes())) return 1 } func (n *NakamaModule) httpRequest(l *lua.LState) int { url := l.CheckString(1) method := l.CheckString(2) Loading Loading @@ -437,13 +469,24 @@ func (n *NakamaModule) usersFetchId(l *lua.LState) int { return 0 } //translate uuid to string bytes // Convert and push the values. lv := l.NewTable() for i, u := range users { // Convert UUIDs to string representation. uid, _ := uuid.FromBytes(u.Id) u.Id = []byte(uid.String()) um := structs.Map(u) lv.RawSetInt(i+1, convertValue(l, um)) metadataMap := make(map[string]interface{}) err = json.Unmarshal(u.Metadata, &metadataMap) if err != nil { l.RaiseError(fmt.Sprintf("failed to convert metadata to json: %s", err.Error())) return 0 } ut := ConvertMap(l, um) ut.RawSetString("Metadata", ConvertMap(l, metadataMap)) lv.RawSetInt(i+1, ut) } l.Push(lv) Loading Loading @@ -474,13 +517,24 @@ func (n *NakamaModule) usersFetchHandle(l *lua.LState) int { return 0 } //translate uuid to string bytes // Convert and push the values. lv := l.NewTable() for i, u := range users { // Convert UUIDs to string representation. uid, _ := uuid.FromBytes(u.Id) u.Id = []byte(uid.String()) um := structs.Map(u) lv.RawSetInt(i+1, convertValue(l, um)) metadataMap := make(map[string]interface{}) err = json.Unmarshal(u.Metadata, &metadataMap) if err != nil { l.RaiseError(fmt.Sprintf("failed to convert metadata to json: %s", err.Error())) return 0 } ut := ConvertMap(l, um) ut.RawSetString("Metadata", ConvertMap(l, metadataMap)) lv.RawSetInt(i+1, ut) } l.Push(lv) Loading @@ -504,8 +558,8 @@ func (n *NakamaModule) usersUpdate(l *lua.LState) int { return } updateTable.ForEach(func(k lua.LValue, v lua.LValue) { update := &SelfUpdateOp{} updateTable.ForEach(func(k lua.LValue, v lua.LValue) { switch k.String() { case "UserId": if v.Type() != lua.LTString { Loading Loading @@ -572,6 +626,7 @@ func (n *NakamaModule) usersUpdate(l *lua.LState) int { conversionError = "unrecognised update key, expects a valid set of user updates" return } }) // Check it's a valid update op. if len(update.UserId) == 0 { Loading @@ -584,7 +639,6 @@ func (n *NakamaModule) usersUpdate(l *lua.LState) int { } updates = append(updates, update) }) }) if conversionError != "" { l.ArgError(1, conversionError) Loading Loading @@ -1293,7 +1347,16 @@ func (n *NakamaModule) leaderboardSubmit(l *lua.LState, op string) int { oid, _ := uuid.FromBytes(record.OwnerId) record.OwnerId = []byte(oid.String()) rm := structs.Map(record) outgoingMetadataMap := make(map[string]interface{}) err = json.Unmarshal(record.Metadata, &outgoingMetadataMap) if err != nil { l.RaiseError(fmt.Sprintf("failed to convert metadata to json: %s", err.Error())) return 0 } lv := ConvertMap(l, rm) lv.RawSetString("Metadata", ConvertMap(l, outgoingMetadataMap)) l.Push(lv) return 1 Loading Loading @@ -1412,13 +1475,24 @@ func (n *NakamaModule) groupsCreate(l *lua.LState) int { return 0 } //translate uuid to string bytes // Convert and push the values. lv := l.NewTable() for i, g := range groups { uid, _ := uuid.FromBytes(g.Id) g.Id = []byte(uid.String()) // Convert UUIDs to string representation. gid, _ := uuid.FromBytes(g.Id) g.Id = []byte(gid.String()) gm := structs.Map(g) lv.RawSetInt(i+1, convertValue(l, gm)) metadataMap := make(map[string]interface{}) err = json.Unmarshal(g.Metadata, &metadataMap) if err != nil { l.RaiseError(fmt.Sprintf("failed to convert metadata to json: %s", err.Error())) return 0 } gt := ConvertMap(l, gm) gt.RawSetString("Metadata", ConvertMap(l, metadataMap)) lv.RawSetInt(i+1, gt) } l.Push(lv) Loading Loading @@ -1550,13 +1624,24 @@ func (n *NakamaModule) groupUsersList(l *lua.LState) int { return 0 } //translate uuid to string bytes // Convert and push the values. lv := l.NewTable() for i, u := range users { // Convert UUIDs to string representation. uid, _ := uuid.FromBytes(u.User.Id) u.User.Id = []byte(uid.String()) um := structs.Map(u) lv.RawSetInt(i+1, convertValue(l, um)) metadataMap := make(map[string]interface{}) err = json.Unmarshal(u.User.Metadata, &metadataMap) if err != nil { l.RaiseError(fmt.Sprintf("failed to convert metadata to json: %s", err.Error())) return 0 } ut := ConvertMap(l, um) ut.RawGetString("User").(*lua.LTable).RawSetString("Metadata", ConvertMap(l, metadataMap)) lv.RawSetInt(i+1, ut) } l.Push(lv) Loading @@ -1582,13 +1667,24 @@ func (n *NakamaModule) groupsUserList(l *lua.LState) int { return 0 } //translate uuid to string bytes // Convert and push the values. lv := l.NewTable() for i, g := range groups { // Convert UUIDs to string representation. gid, _ := uuid.FromBytes(g.Group.Id) g.Group.Id = []byte(gid.String()) gm := structs.Map(g) lv.RawSetInt(i+1, convertValue(l, gm)) metadataMap := make(map[string]interface{}) err = json.Unmarshal(g.Group.Metadata, &metadataMap) if err != nil { l.RaiseError(fmt.Sprintf("failed to convert metadata to json: %s", err.Error())) return 0 } gt := ConvertMap(l, gm) gt.RawGetString("Group").(*lua.LTable).RawSetString("Metadata", ConvertMap(l, metadataMap)) lv.RawSetInt(i+1, gt) } l.Push(lv) Loading Loading
CHANGELOG.md +10 −0 Original line number Diff line number Diff line Loading @@ -4,6 +4,16 @@ All notable changes to this project are documented below. The format is based on [keep a changelog](http://keepachangelog.com/) and this project uses [semantic versioning](http://semver.org/). ## [Unreleased] ### Added - New script runtime functions to convert UUIDs between byte and string representations. ### Changed - Improve index selection in storage list operations. - Payloads in `register_before` hooks now use `PascalCase` field names and expose correctly formatted IDs. - Metadata regions in users, groups, and leaderboard records are now exposed to the script runtime as Lua tables. ### Fixed - Script runtime batch user update operations now process correctly. ## [1.0.0] - 2017-08-01 ### Added Loading
server/core_runtime_hooks.go +2 −27 Original line number Diff line number Diff line Loading @@ -31,16 +31,6 @@ func RuntimeBeforeHook(runtime *Runtime, jsonpbMarshaler *jsonpb.Marshaler, json return envelope, nil } strEnvelope, err := jsonpbMarshaler.MarshalToString(envelope) if err != nil { return nil, err } var jsonEnvelope map[string]interface{} if err = json.Unmarshal([]byte(strEnvelope), &jsonEnvelope); err != nil { return nil, err } userId := uuid.Nil handle := "" expiry := int64(0) Loading @@ -50,22 +40,7 @@ func RuntimeBeforeHook(runtime *Runtime, jsonpbMarshaler *jsonpb.Marshaler, json expiry = session.expiry } result, fnErr := runtime.InvokeFunctionBefore(fn, userId, handle, expiry, jsonEnvelope) if fnErr != nil { return nil, fnErr } bytesEnvelope, err := json.Marshal(result) if err != nil { return nil, err } resultEnvelope := &Envelope{} if err = jsonpbUnmarshaler.Unmarshal(bytes.NewReader(bytesEnvelope), resultEnvelope); err != nil { return nil, err } return resultEnvelope, nil return runtime.InvokeFunctionBefore(fn, userId, handle, expiry, jsonpbMarshaler, jsonpbUnmarshaler, envelope) } func RuntimeAfterHook(logger *zap.Logger, runtime *Runtime, jsonpbMarshaler *jsonpb.Marshaler, messageType string, envelope *Envelope, session *session) { Loading Loading @@ -121,7 +96,7 @@ func RuntimeBeforeHookAuthentication(runtime *Runtime, jsonpbMarshaler *jsonpb.M handle := "" expiry := int64(0) result, fnErr := runtime.InvokeFunctionBefore(fn, userId, handle, expiry, jsonEnvelope) result, fnErr := runtime.InvokeFunctionBeforeAuthentication(fn, userId, handle, expiry, jsonEnvelope) if fnErr != nil { return nil, fnErr } Loading
server/core_storage.go +6 −6 Original line number Diff line number Diff line Loading @@ -105,6 +105,12 @@ func StorageList(logger *zap.Logger, db *sql.DB, caller uuid.UUID, userID []byte // Select the correct index. NOTE: should be removed when DB index selection is smarter. index := "" if len(userID) == 0 { if collection == "" { index = "deleted_at_bucket_read_collection_record_user_id_idx" } else { index = "deleted_at_bucket_collection_read_record_user_id_idx" } } else { if bucket == "" { index = "deleted_at_user_id_read_bucket_collection_record_idx" } else if collection == "" { Loading @@ -112,12 +118,6 @@ func StorageList(logger *zap.Logger, db *sql.DB, caller uuid.UUID, userID []byte } else { index = "deleted_at_user_id_bucket_collection_read_record_idx" } } else { if collection == "" { index = "deleted_at_bucket_read_collection_record_user_id_idx" } else { index = "deleted_at_bucket_collection_read_record_user_id_idx" } } // Set up the query. Loading
server/runtime.go +315 −2 Original line number Diff line number Diff line Loading @@ -24,6 +24,9 @@ import ( "database/sql" "bytes" "encoding/json" "github.com/gogo/protobuf/jsonpb" "github.com/satori/go.uuid" "github.com/yuin/gopher-lua" "go.uber.org/zap" Loading Loading @@ -222,7 +225,35 @@ func (r *Runtime) InvokeFunctionRPC(fn *lua.LFunction, uid uuid.UUID, handle str return nil, errors.New("Runtime function returned invalid data. Only allowed one return value of type String/Byte") } func (r *Runtime) InvokeFunctionBefore(fn *lua.LFunction, uid uuid.UUID, handle string, sessionExpiry int64, payload map[string]interface{}) (map[string]interface{}, error) { func (r *Runtime) InvokeFunctionBefore(fn *lua.LFunction, uid uuid.UUID, handle string, sessionExpiry int64, jsonpbMarshaler *jsonpb.Marshaler, jsonpbUnmarshaler *jsonpb.Unmarshaler, envelope *Envelope) (*Envelope, error) { l, _ := r.NewStateThread() defer l.Close() ctx := NewLuaContext(l, r.luaEnv, BEFORE, uid, handle, sessionExpiry) var lv lua.LValue var err error if envelope != nil { lv, err = ConvertEnvelopeToLTable(l, jsonpbMarshaler, envelope) if err != nil { return nil, err } } retValue, err := r.invokeFunction(l, fn, ctx, lv) if err != nil { return nil, err } if retValue == nil || retValue == lua.LNil { return nil, errors.New("Runtime before hook did not return the payload") } else if retValue.Type() == lua.LTTable { return ConvertLTableToEnvelope(l, jsonpbUnmarshaler, retValue.(*lua.LTable), envelope) } return nil, errors.New("Runtime function returned invalid data. Only allowed one return value of type Table") } func (r *Runtime) InvokeFunctionBeforeAuthentication(fn *lua.LFunction, uid uuid.UUID, handle string, sessionExpiry int64, payload map[string]interface{}) (map[string]interface{}, error) { l, _ := r.NewStateThread() defer l.Close() Loading @@ -238,7 +269,7 @@ func (r *Runtime) InvokeFunctionBefore(fn *lua.LFunction, uid uuid.UUID, handle } if retValue == nil || retValue == lua.LNil { return nil, nil return nil, errors.New("Runtime before hook did not return the payload") } else if retValue.Type() == lua.LTTable { return ConvertLuaTable(retValue.(*lua.LTable)), nil } Loading Loading @@ -312,3 +343,285 @@ func (r *Runtime) invokeFunction(l *lua.LState, fn *lua.LFunction, ctx *lua.LTab func (r *Runtime) Stop() { r.vm.Close() } func ConvertEnvelopeToLTable(l *lua.LState, jsonpbMarshaler *jsonpb.Marshaler, envelope *Envelope) (lua.LValue, error) { lt := l.NewTable() switch envelope.Payload.(type) { case *Envelope_GroupsRemove: ids := l.NewTable() for i, l := range envelope.GetGroupsRemove().GroupIds { gid, err := uuid.FromBytes(l) if err != nil { return nil, errors.New("Invalid Group ID in GroupsRemove conversion to script runtime") } ids.RawSetInt(i+1, lua.LString(gid.String())) } groupIDs := l.NewTable() groupIDs.RawSetString("GroupIds", ids) lt.RawSetString("GroupsRemove", groupIDs) case *Envelope_GroupsJoin: ids := l.NewTable() for i, l := range envelope.GetGroupsJoin().GroupIds { gid, err := uuid.FromBytes(l) if err != nil { return nil, errors.New("Invalid Group ID in GroupsJoin conversion to script runtime") } ids.RawSetInt(i+1, lua.LString(gid.String())) } groupIDs := l.NewTable() groupIDs.RawSetString("GroupIds", ids) lt.RawSetString("GroupsJoin", groupIDs) case *Envelope_GroupsLeave: ids := l.NewTable() for i, l := range envelope.GetGroupsLeave().GroupIds { gid, err := uuid.FromBytes(l) if err != nil { return nil, errors.New("Invalid Group ID in GroupsLeave conversion to script runtime") } ids.RawSetInt(i+1, lua.LString(gid.String())) } groupIDs := l.NewTable() groupIDs.RawSetString("GroupIds", ids) lt.RawSetString("GroupsLeave", groupIDs) case *Envelope_GroupUsersKick: pairs := l.NewTable() for i, p := range envelope.GetGroupUsersKick().GroupUsers { gid, err := uuid.FromBytes(p.GroupId) if err != nil { return nil, errors.New("Invalid Group ID in GroupUsersKick conversion to script runtime") } uid, err := uuid.FromBytes(p.UserId) if err != nil { return nil, errors.New("Invalid User ID in GroupUsersKick conversion to script runtime") } pair := l.NewTable() pair.RawSetString("GroupId", lua.LString(gid.String())) pair.RawSetString("UserId", lua.LString(uid.String())) pairs.RawSetInt(i+1, pair) } kicks := l.NewTable() kicks.RawSetString("GroupUsers", pairs) lt.RawSetString("GroupUsersKick", kicks) case *Envelope_GroupUsersPromote: pairs := l.NewTable() for i, p := range envelope.GetGroupUsersPromote().GroupUsers { gid, err := uuid.FromBytes(p.GroupId) if err != nil { return nil, errors.New("Invalid Group ID in GroupUsersPromote conversion to script runtime") } uid, err := uuid.FromBytes(p.UserId) if err != nil { return nil, errors.New("Invalid User ID in GroupUsersPromote conversion to script runtime") } pair := l.NewTable() pair.RawSetString("GroupId", lua.LString(gid.String())) pair.RawSetString("UserId", lua.LString(uid.String())) pairs.RawSetInt(i+1, pair) } kicks := l.NewTable() kicks.RawSetString("GroupUsers", pairs) lt.RawSetString("GroupUsersPromote", kicks) default: strEnvelope, err := jsonpbMarshaler.MarshalToString(envelope) if err != nil { return nil, err } var jsonEnvelope map[string]interface{} if err = json.Unmarshal([]byte(strEnvelope), &jsonEnvelope); err != nil { return nil, err } for k, v := range jsonEnvelope { lt.RawSetString(k, convertValue(l, v)) } } return lt, nil } func ConvertLTableToEnvelope(l *lua.LState, jsonpbUnmarshaler *jsonpb.Unmarshaler, lt *lua.LTable, envelope *Envelope) (*Envelope, error) { switch envelope.Payload.(type) { case *Envelope_GroupsRemove: groupsRemove := lt.RawGetString("GroupsRemove") if groupsRemove.Type() != lua.LTTable { return nil, errors.New("Invalid payload in GroupsRemove conversion from script runtime") } groupIds := groupsRemove.(*lua.LTable).RawGetString("GroupIds") if groupIds.Type() != lua.LTTable { return nil, errors.New("Invalid payload in GroupsRemove conversion from script runtime") } ids := make([][]byte, 0) var err error groupIds.(*lua.LTable).ForEach(func(k lua.LValue, v lua.LValue) { if v.Type() != lua.LTString { err = errors.New("Invalid Group ID in GroupsRemove conversion from script runtime") return } gid, e := uuid.FromString(v.String()) if e != nil { err = errors.New("Invalid Group ID in GroupsRemove conversion from script runtime") return } ids = append(ids, gid.Bytes()) }) if err != nil { return nil, err } envelope.GetGroupsRemove().GroupIds = ids case *Envelope_GroupsJoin: groupsJoin := lt.RawGetString("GroupsJoin") if groupsJoin.Type() != lua.LTTable { return nil, errors.New("Invalid payload in GroupsJoin conversion from script runtime") } groupIds := groupsJoin.(*lua.LTable).RawGetString("GroupIds") if groupIds.Type() != lua.LTTable { return nil, errors.New("Invalid payload in GroupsJoin conversion from script runtime") } ids := make([][]byte, 0) var err error groupIds.(*lua.LTable).ForEach(func(k lua.LValue, v lua.LValue) { if v.Type() != lua.LTString { err = errors.New("Invalid Group ID in GroupsJoin conversion from script runtime") return } gid, e := uuid.FromString(v.String()) if e != nil { err = errors.New("Invalid Group ID in GroupsJoin conversion from script runtime") return } ids = append(ids, gid.Bytes()) }) if err != nil { return nil, err } envelope.GetGroupsJoin().GroupIds = ids case *Envelope_GroupsLeave: groupsLeave := lt.RawGetString("GroupsLeave") if groupsLeave.Type() != lua.LTTable { return nil, errors.New("Invalid payload in GroupsLeave conversion from script runtime") } groupIds := groupsLeave.(*lua.LTable).RawGetString("GroupIds") if groupIds.Type() != lua.LTTable { return nil, errors.New("Invalid payload in GroupsLeave conversion from script runtime") } ids := make([][]byte, 0) var err error groupIds.(*lua.LTable).ForEach(func(k lua.LValue, v lua.LValue) { if v.Type() != lua.LTString { err = errors.New("Invalid Group ID in GroupsLeave conversion from script runtime") return } gid, e := uuid.FromString(v.String()) if e != nil { err = errors.New("Invalid Group ID in GroupsLeave conversion from script runtime") return } ids = append(ids, gid.Bytes()) }) if err != nil { return nil, err } envelope.GetGroupsLeave().GroupIds = ids case *Envelope_GroupUsersKick: groupUsersKick := lt.RawGetString("GroupUsersKick") if groupUsersKick.Type() != lua.LTTable { return nil, errors.New("Invalid payload in GroupUsersKick conversion from script runtime") } groupUsers := groupUsersKick.(*lua.LTable).RawGetString("GroupUsers") if groupUsers.Type() != lua.LTTable { return nil, errors.New("Invalid payload in GroupUsersKick conversion from script runtime") } gu := make([]*TGroupUsersKick_GroupUserKick, 0) var err error groupUsers.(*lua.LTable).ForEach(func(k lua.LValue, v lua.LValue) { if v.Type() != lua.LTTable { err = errors.New("Invalid Group User pair in GroupUsersKick conversion from script runtime") return } vt := v.(*lua.LTable) g := vt.RawGetString("GroupId") if g.Type() != lua.LTString { err = errors.New("Invalid Group ID in GroupUsersKick conversion from script runtime") return } gid, e := uuid.FromString(g.String()) if e != nil { err = errors.New("Invalid Group ID in GroupUsersKick conversion from script runtime") return } u := vt.RawGetString("UserId") if u.Type() != lua.LTString { err = errors.New("Invalid User ID in GroupUsersKick conversion from script runtime") return } uid, e := uuid.FromString(u.String()) if e != nil { err = errors.New("Invalid User ID in GroupUsersKick conversion from script runtime") return } gu = append(gu, &TGroupUsersKick_GroupUserKick{GroupId: gid.Bytes(), UserId: uid.Bytes()}) }) if err != nil { return nil, err } envelope.GetGroupUsersKick().GroupUsers = gu case *Envelope_GroupUsersPromote: groupUsersPromote := lt.RawGetString("GroupUsersPromote") if groupUsersPromote.Type() != lua.LTTable { return nil, errors.New("Invalid payload in GroupUsersPromote conversion from script runtime") } groupUsers := groupUsersPromote.(*lua.LTable).RawGetString("GroupUsers") if groupUsers.Type() != lua.LTTable { return nil, errors.New("Invalid payload in GroupUsersPromote conversion from script runtime") } gu := make([]*TGroupUsersPromote_GroupUserPromote, 0) var err error groupUsers.(*lua.LTable).ForEach(func(k lua.LValue, v lua.LValue) { if v.Type() != lua.LTTable { err = errors.New("Invalid Group User pair in GroupUsersPromote conversion from script runtime") return } vt := v.(*lua.LTable) g := vt.RawGetString("GroupId") if g.Type() != lua.LTString { err = errors.New("Invalid Group ID in GroupUsersPromote conversion from script runtime") return } gid, e := uuid.FromString(g.String()) if e != nil { err = errors.New("Invalid Group ID in GroupUsersPromote conversion from script runtime") return } u := vt.RawGetString("UserId") if u.Type() != lua.LTString { err = errors.New("Invalid User ID in GroupUsersPromote conversion from script runtime") return } uid, e := uuid.FromString(u.String()) if e != nil { err = errors.New("Invalid User ID in GroupUsersPromote conversion from script runtime") return } gu = append(gu, &TGroupUsersPromote_GroupUserPromote{GroupId: gid.Bytes(), UserId: uid.Bytes()}) }) if err != nil { return nil, err } envelope.GetGroupUsersPromote().GroupUsers = gu default: result := ConvertLuaTable(lt) bytesEnvelope, err := json.Marshal(result) if err != nil { return nil, err } if err = jsonpbUnmarshaler.Unmarshal(bytes.NewReader(bytesEnvelope), envelope); err != nil { return nil, err } } return envelope, nil }
server/runtime_nakama_module.go +120 −24 Original line number Diff line number Diff line Loading @@ -77,6 +77,8 @@ func NewNakamaModule(logger *zap.Logger, db *sql.DB, l *lua.LState, notification func (n *NakamaModule) Loader(l *lua.LState) int { mod := l.SetFuncs(l.NewTable(), map[string]lua.LGFunction{ "uuid_v4": n.uuidV4, "uuid_bytes_to_string": n.uuidBytesToString, "uuid_string_to_bytes": n.uuidStringToBytes, "http_request": n.httpRequest, "json_encode": n.jsonEncode, "json_decode": n.jsonDecode, Loading Loading @@ -122,6 +124,36 @@ func (n *NakamaModule) uuidV4(l *lua.LState) int { return 1 } func (n *NakamaModule) uuidBytesToString(l *lua.LState) int { uuidBytes := l.CheckString(1) if uuidBytes == "" { l.ArgError(1, "Expects a UUID byte string") return 0 } u, err := uuid.FromBytes([]byte(uuidBytes)) if err != nil { l.ArgError(1, "Not a valid UUID byte string") return 0 } l.Push(lua.LString(u.String())) return 1 } func (n *NakamaModule) uuidStringToBytes(l *lua.LState) int { uuidString := l.CheckString(1) if uuidString == "" { l.ArgError(1, "Expects a UUID string") return 0 } u, err := uuid.FromString(uuidString) if err != nil { l.ArgError(1, "Not a valid UUID string") return 0 } l.Push(lua.LString(u.Bytes())) return 1 } func (n *NakamaModule) httpRequest(l *lua.LState) int { url := l.CheckString(1) method := l.CheckString(2) Loading Loading @@ -437,13 +469,24 @@ func (n *NakamaModule) usersFetchId(l *lua.LState) int { return 0 } //translate uuid to string bytes // Convert and push the values. lv := l.NewTable() for i, u := range users { // Convert UUIDs to string representation. uid, _ := uuid.FromBytes(u.Id) u.Id = []byte(uid.String()) um := structs.Map(u) lv.RawSetInt(i+1, convertValue(l, um)) metadataMap := make(map[string]interface{}) err = json.Unmarshal(u.Metadata, &metadataMap) if err != nil { l.RaiseError(fmt.Sprintf("failed to convert metadata to json: %s", err.Error())) return 0 } ut := ConvertMap(l, um) ut.RawSetString("Metadata", ConvertMap(l, metadataMap)) lv.RawSetInt(i+1, ut) } l.Push(lv) Loading Loading @@ -474,13 +517,24 @@ func (n *NakamaModule) usersFetchHandle(l *lua.LState) int { return 0 } //translate uuid to string bytes // Convert and push the values. lv := l.NewTable() for i, u := range users { // Convert UUIDs to string representation. uid, _ := uuid.FromBytes(u.Id) u.Id = []byte(uid.String()) um := structs.Map(u) lv.RawSetInt(i+1, convertValue(l, um)) metadataMap := make(map[string]interface{}) err = json.Unmarshal(u.Metadata, &metadataMap) if err != nil { l.RaiseError(fmt.Sprintf("failed to convert metadata to json: %s", err.Error())) return 0 } ut := ConvertMap(l, um) ut.RawSetString("Metadata", ConvertMap(l, metadataMap)) lv.RawSetInt(i+1, ut) } l.Push(lv) Loading @@ -504,8 +558,8 @@ func (n *NakamaModule) usersUpdate(l *lua.LState) int { return } updateTable.ForEach(func(k lua.LValue, v lua.LValue) { update := &SelfUpdateOp{} updateTable.ForEach(func(k lua.LValue, v lua.LValue) { switch k.String() { case "UserId": if v.Type() != lua.LTString { Loading Loading @@ -572,6 +626,7 @@ func (n *NakamaModule) usersUpdate(l *lua.LState) int { conversionError = "unrecognised update key, expects a valid set of user updates" return } }) // Check it's a valid update op. if len(update.UserId) == 0 { Loading @@ -584,7 +639,6 @@ func (n *NakamaModule) usersUpdate(l *lua.LState) int { } updates = append(updates, update) }) }) if conversionError != "" { l.ArgError(1, conversionError) Loading Loading @@ -1293,7 +1347,16 @@ func (n *NakamaModule) leaderboardSubmit(l *lua.LState, op string) int { oid, _ := uuid.FromBytes(record.OwnerId) record.OwnerId = []byte(oid.String()) rm := structs.Map(record) outgoingMetadataMap := make(map[string]interface{}) err = json.Unmarshal(record.Metadata, &outgoingMetadataMap) if err != nil { l.RaiseError(fmt.Sprintf("failed to convert metadata to json: %s", err.Error())) return 0 } lv := ConvertMap(l, rm) lv.RawSetString("Metadata", ConvertMap(l, outgoingMetadataMap)) l.Push(lv) return 1 Loading Loading @@ -1412,13 +1475,24 @@ func (n *NakamaModule) groupsCreate(l *lua.LState) int { return 0 } //translate uuid to string bytes // Convert and push the values. lv := l.NewTable() for i, g := range groups { uid, _ := uuid.FromBytes(g.Id) g.Id = []byte(uid.String()) // Convert UUIDs to string representation. gid, _ := uuid.FromBytes(g.Id) g.Id = []byte(gid.String()) gm := structs.Map(g) lv.RawSetInt(i+1, convertValue(l, gm)) metadataMap := make(map[string]interface{}) err = json.Unmarshal(g.Metadata, &metadataMap) if err != nil { l.RaiseError(fmt.Sprintf("failed to convert metadata to json: %s", err.Error())) return 0 } gt := ConvertMap(l, gm) gt.RawSetString("Metadata", ConvertMap(l, metadataMap)) lv.RawSetInt(i+1, gt) } l.Push(lv) Loading Loading @@ -1550,13 +1624,24 @@ func (n *NakamaModule) groupUsersList(l *lua.LState) int { return 0 } //translate uuid to string bytes // Convert and push the values. lv := l.NewTable() for i, u := range users { // Convert UUIDs to string representation. uid, _ := uuid.FromBytes(u.User.Id) u.User.Id = []byte(uid.String()) um := structs.Map(u) lv.RawSetInt(i+1, convertValue(l, um)) metadataMap := make(map[string]interface{}) err = json.Unmarshal(u.User.Metadata, &metadataMap) if err != nil { l.RaiseError(fmt.Sprintf("failed to convert metadata to json: %s", err.Error())) return 0 } ut := ConvertMap(l, um) ut.RawGetString("User").(*lua.LTable).RawSetString("Metadata", ConvertMap(l, metadataMap)) lv.RawSetInt(i+1, ut) } l.Push(lv) Loading @@ -1582,13 +1667,24 @@ func (n *NakamaModule) groupsUserList(l *lua.LState) int { return 0 } //translate uuid to string bytes // Convert and push the values. lv := l.NewTable() for i, g := range groups { // Convert UUIDs to string representation. gid, _ := uuid.FromBytes(g.Group.Id) g.Group.Id = []byte(gid.String()) gm := structs.Map(g) lv.RawSetInt(i+1, convertValue(l, gm)) metadataMap := make(map[string]interface{}) err = json.Unmarshal(g.Group.Metadata, &metadataMap) if err != nil { l.RaiseError(fmt.Sprintf("failed to convert metadata to json: %s", err.Error())) return 0 } gt := ConvertMap(l, gm) gt.RawGetString("Group").(*lua.LTable).RawSetString("Metadata", ConvertMap(l, metadataMap)) lv.RawSetInt(i+1, gt) } l.Push(lv) Loading