Server-side WebSocket adapter for JSON-RPC plus MPP payment challenges.
Consumers bring their own WebSocket library (Bandit, Cowboy, …). This
module speaks decoded text frames — open/1 and handle_text/2 return
JSON strings ready to push.
Payment challenges ride on the subscription handshake: open/1 emits a
challenge frame before application traffic. The client answers with a
credential frame (Payment <base64url>). A successful verify yields a
receipt frame; JSON-RPC then travels in message frames.
Wire format matches mpp-rs server::ws / alloy-transport-mpp
(refs/mpp-rs/src/server/ws.rs,
refs/mpp-rs/crates/alloy-transport-mpp/src/ws.rs):
- Client → server:
credential,message - Server → client:
challenge,message,needVoucher,receipt,error
API Functions
| Function | Arity | Description | Param Kinds |
|---|---|---|---|
message_frame | 1 | Build a server message frame wrapping a JSON-RPC payload as a JSON string. | data: value |
error_frame | 1 | Build a server error frame. | error: value |
receipt_frame | 1 | Build a server receipt frame. | receipt: value |
need_voucher_frame | 1 | Build a server needVoucher frame for a session top-up. | attrs: value |
challenge_frame | 2 | Build a server challenge frame from an MPP.Challenge. | challenge: value, error: value |
encode_frame | 1 | Serialize an MPP frame map to a WebSocket text payload. | frame: value |
decode_frame | 1 | Parse a WebSocket text payload into an MPP frame map. | text: value |
handle_frame | 2 | Handle one already-decoded MPP frame map. | frame: value, session: value |
handle_text | 2 | Handle one inbound WebSocket text frame and return outbound frames to push. | text: value, session: value |
open | 1 | Emit the subscription-handshake challenge frame for a newly accepted socket. | session: value |
init | 1 | Build a WebSocket session from the same endpoint options as MPP.Plug, plus a JSON-RPC :handler. | opts: value |
Summary
Functions
Build a server challenge frame from an MPP.Challenge.
Parse a WebSocket text payload into an MPP frame map.
Serialize an MPP frame map to a WebSocket text payload.
Build a server error frame.
Handle one already-decoded MPP frame map.
Handle one inbound WebSocket text frame and return outbound frames to push.
Build a WebSocket session from the same endpoint options as MPP.Plug, plus a JSON-RPC :handler.
Build a server message frame wrapping a JSON-RPC payload as a JSON string.
Build a server needVoucher frame for a session top-up.
Emit the subscription-handshake challenge frame for a newly accepted socket.
Build a server receipt frame.
Types
@type status() :: :open | :authorized
@type t() :: %MPP.Transports.WebSocket{ challenge: MPP.Challenge.t() | nil, config: MPP.Plug.Config.t(), handler: (map() -> term()), status: status() }
Functions
@spec challenge_frame(MPP.Challenge.t(), String.t() | nil) :: map()
Build a server challenge frame from an MPP.Challenge.
Parameters
challenge- Challenge struct (value)error- Optional error string carried next to the challenge (value)
Returns
Frame map (map)
# descripex:contract
%{
params: %{
error: %{
description: "Optional error string carried next to the challenge",
kind: :value
},
challenge: %{description: "Challenge struct", kind: :value}
},
returns: %{type: :map, description: "Frame map"}
}
Parse a WebSocket text payload into an MPP frame map.
Parameters
text- Raw WebSocket text payload (value)
Returns
{:ok, frame} or {:error, :malformed_frame | :unknown_frame} (tagged_tuple)
Errors
:malformed_frame:unknown_frame
# descripex:contract
%{
params: %{text: %{description: "Raw WebSocket text payload", kind: :value}},
errors: [:malformed_frame, :unknown_frame],
returns: %{
type: :tagged_tuple,
description: "`{:ok, frame}` or `{:error, :malformed_frame | :unknown_frame}`"
}
}
Serialize an MPP frame map to a WebSocket text payload.
Parameters
frame- Frame map with atypediscriminator (value)
Returns
JSON text (string)
# descripex:contract
%{
params: %{
frame: %{description: "Frame map with a `type` discriminator", kind: :value}
},
returns: %{type: :string, description: "JSON text"}
}
Build a server error frame.
Parameters
error- Error message (value)
Returns
Frame map (map)
# descripex:contract
%{
params: %{error: %{description: "Error message", kind: :value}},
returns: %{type: :map, description: "Frame map"}
}
Handle one already-decoded MPP frame map.
Parameters
frame- Decoded frame map with atypediscriminator (value)session- Current session (value)
Returns
{session, [frame_map]} (tuple)
# descripex:contract
%{
params: %{
session: %{description: "Current session", kind: :value},
frame: %{
description: "Decoded frame map with a `type` discriminator",
kind: :value
}
},
returns: %{type: :tuple, description: "`{session, [frame_map]}`"}
}
Handle one inbound WebSocket text frame and return outbound frames to push.
Parameters
text- Raw WebSocket text payload (value)session- Current session (value)
Returns
{session, [json_text]} (tuple)
# descripex:contract
%{
params: %{
session: %{description: "Current session", kind: :value},
text: %{description: "Raw WebSocket text payload", kind: :value}
},
returns: %{type: :tuple, description: "`{session, [json_text]}`"}
}
Build a WebSocket session from the same endpoint options as MPP.Plug, plus a JSON-RPC :handler.
Parameters
opts- Keyword options including :handler, :secret_key, :realm, and one or more payment methods (value)
Returns
MPP.Transports.WebSocket session (struct)
# descripex:contract
%{
params: %{
opts: %{
description: "Keyword options including :handler, :secret_key, :realm, and one or more payment methods",
kind: :value
}
},
returns: %{type: :struct, description: "`MPP.Transports.WebSocket` session"}
}
Build a server message frame wrapping a JSON-RPC payload as a JSON string.
Parameters
data- JSON-RPC envelope, or an already-encoded JSON string (value)
Returns
Frame map (map)
# descripex:contract
%{
params: %{
data: %{
description: "JSON-RPC envelope, or an already-encoded JSON string",
kind: :value
}
},
returns: %{type: :map, description: "Frame map"}
}
Build a server needVoucher frame for a session top-up.
Parameters
attrs- Map or keyword with channelId/requiredCumulative/acceptedCumulative/deposit (value)
Returns
Frame map (map)
# descripex:contract
%{
params: %{
attrs: %{
description: "Map or keyword with channelId/requiredCumulative/acceptedCumulative/deposit",
kind: :value
}
},
returns: %{type: :map, description: "Frame map"}
}
Emit the subscription-handshake challenge frame for a newly accepted socket.
Parameters
session- Session from init/1 (value)
Returns
{session, [json_text]} — push the texts as WebSocket text frames (tuple)
# descripex:contract
%{
params: %{session: %{description: "Session from init/1", kind: :value}},
returns: %{
type: :tuple,
description: "`{session, [json_text]}` — push the texts as WebSocket text frames"
}
}
Build a server receipt frame.
Parameters
receipt- Receipt map as sent on the wire (value)
Returns
Frame map (map)
# descripex:contract
%{
params: %{
receipt: %{description: "Receipt map as sent on the wire", kind: :value}
},
returns: %{type: :map, description: "Frame map"}
}