MPP.Transports.WebSocket (mpp v0.16.0)

Copy Markdown View Source

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

FunctionArityDescriptionParam Kinds
message_frame1Build a server message frame wrapping a JSON-RPC payload as a JSON string.data: value
error_frame1Build a server error frame.error: value
receipt_frame1Build a server receipt frame.receipt: value
need_voucher_frame1Build a server needVoucher frame for a session top-up.attrs: value
challenge_frame2Build a server challenge frame from an MPP.Challenge.challenge: value, error: value
encode_frame1Serialize an MPP frame map to a WebSocket text payload.frame: value
decode_frame1Parse a WebSocket text payload into an MPP frame map.text: value
handle_frame2Handle one already-decoded MPP frame map.frame: value, session: value
handle_text2Handle one inbound WebSocket text frame and return outbound frames to push.text: value, session: value
open1Emit the subscription-handshake challenge frame for a newly accepted socket.session: value
init1Build 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

status()

@type status() :: :open | :authorized

t()

@type t() :: %MPP.Transports.WebSocket{
  challenge: MPP.Challenge.t() | nil,
  config: MPP.Plug.Config.t(),
  handler: (map() -> term()),
  status: status()
}

Functions

challenge_frame(challenge, error \\ nil)

@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"}
}

decode_frame(text)

@spec decode_frame(String.t()) ::
  {:ok, map()} | {:error, :malformed_frame | :unknown_frame}

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}`"
  }
}

encode_frame(frame)

@spec encode_frame(map()) :: String.t()

Serialize an MPP frame map to a WebSocket text payload.

Parameters

  • frame - Frame map with a type discriminator (value)

Returns

JSON text (string)

# descripex:contract
%{
  params: %{
    frame: %{description: "Frame map with a `type` discriminator", kind: :value}
  },
  returns: %{type: :string, description: "JSON text"}
}

error_frame(error)

@spec error_frame(String.t()) :: map()

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_frame(frame, session)

@spec handle_frame(map(), t()) :: {t(), [map()]}

Handle one already-decoded MPP frame map.

Parameters

  • frame - Decoded frame map with a type discriminator (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_text(text, session)

@spec handle_text(String.t(), t()) :: {t(), [String.t()]}

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]}`"}
}

init(opts)

@spec init(keyword()) :: t()

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"}
}

message_frame(data)

@spec message_frame(term()) :: map()

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"}
}

need_voucher_frame(attrs)

@spec need_voucher_frame(keyword() | map()) :: 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"}
}

open(session)

@spec open(t()) :: {t(), [String.t()]}

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"
  }
}

receipt_frame(receipt)

@spec receipt_frame(map()) :: map()

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"}
}