Ambassador - Client-Server communication made easy

Ambassador is an open source library that lets you send arbitrary data over from the server to the client, and vice versa.

Currently, you can send strings, numbers, booleans, tables (no metatables), functions, enums, and instances. Always add sanity checks, even when using Ambassador.

NOTE: A wrapper is generated when a function is sent. As such, the received function will yield, since it makes a call to InvokeServer or InvokeClient.

Why not just use RemoteFunctions and RemoteEvents?

Ambassador uses RemoteFunctions under the hood and builds on them, by encoding data that cannot be sent through regular RemoteFunctions. Instead of setting up remotes, keeping track of them, invoking them, and encoding data manually, Ambassador does all of that for you. All you need to do is invoke it with a name, and a piece of data.

Example

We can send a function from the client to the server which, when called, will run locally and set the time to whatever the server requests:

Ambassador:Send("setLocalTime", function(time)
    game.Lighting.TimeOfDay = time
end)

On the server, we want to receive this setLocalTime function and invoke it. In case of an error, we will kick the player. When the player leaves, we also have to invoke Ambassador:Cleanup to clean up any player-specific remotes that the library created.

game.Players.PlayerAdded:Connect(function(player)
    local success, result = Ambassador:Await("setLocalTime", player)
    if success then
        -- if the call succeeds, result stores the setLocalTime function
        result(math.random(0, 24))
    else
        player:Kick(result) -- result stores the error in case of a failure
    end
end)

game.Players.PlayerRemoving:Connect(function(player)
    Ambassador:Cleanup(player) -- Clean up any left-over remotes
end)

Using Ambassador

Rojo

Clone the git repository into a folder, e.g. src/lib. Then, require it as you would any Roblox module.

Studio

Copy the contents of init.lua and paste them into a ModuleScript in ReplicatedStorage. Then, require the ModuleScript for use in your scripts.


Sanity checks are still required just as they always were. Ambassador is a quality of life improvement, not a security improvement.

Documentation can be found here.

Please let me know if you encounter any bugs or have any further questions.

60 Likes

Really neat.
Will be using for sure.

2 Likes

Good work! I will definitely look into this.

What does this mean, exactly?

1 Like

If the client doesn’t respond in 30 seconds, it will return false, "Timeout", in which case something’s clearly gone wrong. It’s also reserved for the future in case more errors might pop up during Ambassador’s initialization.

2 Likes

Okay, but why kick the player if it fails? That seems like the opposite of what you’d want to do (and could result in loss of players playing the game).

If an error occurs while initializing Ambassador, that could cause errors later in the script. Perhaps a better way to do it is kick the player using the message "An error has occurred: " .. result .. ". Please rejoin the game."

Practically, Roblox would crash or kick the player long before the timeout hits if it ever does, so it’s really just a precaution to prevent further issues in your game. As always though, if you have a way of recovering from such an error, you’re not obligated to kick the player.

4 Likes

I think this is also a contingency, as the client can delete a remote function, and as such, they wouldn’t receive any events, practically leaving the server in escrow.

1 Like

That’s a creative way to implement passing functions! I like it.

I skimmed through the source and noticed that it needlessly trusts the client at one point:

So it allows clients to pass any Player object as data.player, which may result in the server sending remote function calls to the wrong Player. So, the server code should propagate the player to all decode function calls.

3 Likes

Good catch! I’ll fix that right away.

Edit: The issue should now be fixed.

2 Likes

This is actually really cool. I never thought I’d need to send functions from the server to the client but now that I’ve seen your examples this looks like a huge time saver!

Nice work, I’ll give it a try!

Update: Instances can now be passed around between server and client.

1 Like

This is neat, well done. I definitely like the idea!

This is not secure, it is not safe to invoke RemoteFunctions to the client. Ambassador should be avoided while this is the case.

Edit: Since I was asked to explain why it is unsafe to use RemoteFunctions, the reasoning is that they do not timeout and will never return if the client does not set the callback. The client can delay this infinitely and maliciously. Wrapping the invocation inside a new thread (which is not done in Ambassador yet) is still not a good solution and can unnecessarily waste memory over time. It’s better to implement this functionality with a wrapper over RemoteEvents instead. Going from client -> sever is fine.

6 Likes

Native support for timeouts in RF invocations would actually be a great feature request for the Roblox engine. That being said, does anybody know if threads waiting on :InvokeClient() are GC’d when the target client is disconnected? If that is the case, wrapping the call in a thread would not cause serious issues.

I admire the idea of calling functions on the Server which exist on the client. I know that’s pretty much the same as RemoteEvents/Functions, but it feels different and cool. It’s kinda like how differently shaped noodles “taste different”.

1 Like

Seems to be throwing an error.
image

1 Like

Any update to fixing this, or is this dead release?

If you could provide an example piece of code that results in this error, I will try to fix it, but I can’t seem to tell what the issue might be. Also, as a side note, I’ve been away a few days for personal reasons, so sorry for the delayed reply.