Networking

Use namespaced JSON messages with host-resolved callers and bounded transport policy.

Message contract

Type or memberContract
LitherarpAddonNetworkDirectionFlags: ClientToHost, HostToClient, Both.
LitherarpAddonNetworkOptions.MaxPayloadBytesReceiver limit, clamped from 1 to 16,384 UTF-8 bytes. Default 4,096.
MaxCallsPerWindowPer-caller host limit, clamped from 1 to 100. Default 20.
RateLimitWindowSecondsWindow clamped from 0.25 to 60 seconds. Default 5.
PermissionNodeOptional local node resolved as addon.<id>.<node> on every client-to-host message.
LitherarpAddonNetworkMessageContextAddonId, MessageName, cloned JSON Payload, nullable Caller, nullable host-resolved Player, and TryRead<T>.

Request and response

1public sealed record PurchaseRequest( string ItemId );
2public sealed record PurchaseResult( bool Success, string Message );
3
4public void OnHostLoaded( LitherarpAddonContext context )
5{
6 context.RegisterNetworkMessage(
7 "purchase.request",
8 LitherarpAddonNetworkDirection.ClientToHost,
9 message =>
10 {
11 if ( !message.TryRead<PurchaseRequest>( out var request ) || request is null )
12 return;
13
14 // The permission option below requires an existing caller pawn.
15 var player = message.Player;
16 if ( player is null )
17 return;
18
19 var result = ValidatePurchase( player, request.ItemId );
20 context.SendNetworkToPlayer(
21 player,
22 "purchase.result",
23 new PurchaseResult( result.Success, result.Message ) );
24 },
25 new LitherarpAddonNetworkOptions
26 {
27 MaxPayloadBytes = 1024,
28 MaxCallsPerWindow = 6,
29 RateLimitWindowSeconds = 5f,
30 PermissionNode = "shop.use"
31 } );
32
33 // No PermissionNode: this handshake is accepted before pawn creation.
34 context.RegisterNetworkMessage(
35 "ready.hello",
36 LitherarpAddonNetworkDirection.ClientToHost,
37 message => context.SendNetworkToConnection(
38 message.Caller,
39 "ready.reply",
40 new { accepted = true } ) );
41}
42
43public void OnClientLoaded( LitherarpAddonContext context )
44{
45 context.RegisterNetworkMessage(
46 "purchase.result",
47 LitherarpAddonNetworkDirection.HostToClient,
48 message =>
49 {
50 if ( message.TryRead<PurchaseResult>( out var result ) )
51 ShowResult( result! );
52 } );
53}

Host validation

For client-to-host traffic, the platform validates addon id, message name, registered direction and a positive caller Steam id first. It then spends the caller's rate-limit token and checks any configured permission before measuring or parsing the JSON. Malformed traffic cannot avoid throttling. The client cannot supply Caller or Player.

A message without PermissionNode can run before pawn creation. In that case Caller is set and Player is null. A permission-gated message requires an existing pawn because the host must evaluate addon.<id>.<node> for that player. Use SendNetworkToConnection to answer an active connection before its pawn exists, and SendNetworkToPlayer after it does.

Host-to-client traffic is accepted only from the host RPC path. On a listen server, direct delivery uses the same registered handler.

Limits and failures

  • At most 64 distinct message names per addon.
  • Message names are at most 48 characters and are normalized to lowercase. Allowed characters are ASCII letters, digits, dot, underscore and hyphen.
  • The sender-wide serialization ceiling is 16,384 UTF-8 bytes. The receiver can set a lower limit.
  • Three consecutive recoverable handler failures quarantine that handler for the activation.
  • Nested synchronous dispatch is rejected after a depth of eight.
  • All payloads must be JSON serializable. A false return means registration, validation or send failed.
Raw RPC methods are your responsibility

A custom addon [Rpc.Host] method does not inherit these caller, permission, payload and rate checks. Use the addon network service unless you implement every guard yourself.