Skip to content

feat: add WebSocket transport support#146

Open
decofe wants to merge 20 commits intomainfrom
georgen/transport-trait
Open

feat: add WebSocket transport support#146
decofe wants to merge 20 commits intomainfrom
georgen/transport-trait

Conversation

@decofe
Copy link
Member

@decofe decofe commented Mar 24, 2026

Changes

Adds WebSocket transport for bidirectional session payments (TOOLS-323).

Transport trait abstraction

  • server::transport::Transport — abstracts credential extraction, challenge response, and receipt attachment (HTTP impl included)
  • client::transport::Transport — abstracts payment detection, challenge extraction, and credential attachment (HTTP impl included)

WebSocket transport

  • server::ws / client::ws — WebSocket transport with JSON message protocol (credential, challenge, message, needVoucher, receipt, error)
  • server::ws_session — metered streaming over generic Sink/Stream (framework-agnostic equivalent of SSE metering)
  • ws feature flag gating tokio-tungstenite + futures-util

Example & tests

  • examples/ws/ — working server + client example using mock charge method (runs locally out of the box)
  • tests/integration_ws.rs — e2e tests over real WS connections

Testing

cargo test --features ws ws
cargo test --features integration-ws --test integration_ws

Co-Authored-By: grandizzy 38490174+grandizzy@users.noreply.github.com

Prompted by: georgen

@github-actions
Copy link
Contributor

github-actions bot commented Mar 24, 2026

Tempo Lint Results

Summary

Found 710 issue(s) across 45 file(s)

Severity Count
Errors 0
Warnings 710
Hints 0

Issues by Rule Type

no-unwrap-in-lib (696 occurrences)
  • /home/runner/work/mpp-rs/mpp-rs/src/body_digest.rs:1 - Avoid .unwrap() in library code. Use proper error handling with ? or .expect() with context.
  • /home/runner/work/mpp-rs/mpp-rs/src/client/ws.rs:1 - Avoid .unwrap() in library code. Use proper error handling with ? or .expect() with context.
  • /home/runner/work/mpp-rs/mpp-rs/src/client/ws.rs:1 - Avoid .unwrap() in library code. Use proper error handling with ? or .expect() with context.
  • /home/runner/work/mpp-rs/mpp-rs/src/client/ws.rs:1 - Avoid .unwrap() in library code. Use proper error handling with ? or .expect() with context.
  • /home/runner/work/mpp-rs/mpp-rs/src/client/fetch.rs:1 - Avoid .unwrap() in library code. Use proper error handling with ? or .expect() with context.
  • /home/runner/work/mpp-rs/mpp-rs/src/client/fetch.rs:1 - Avoid .unwrap() in library code. Use proper error handling with ? or .expect() with context.
  • /home/runner/work/mpp-rs/mpp-rs/src/client/fetch.rs:1 - Avoid .unwrap() in library code. Use proper error handling with ? or .expect() with context.
  • /home/runner/work/mpp-rs/mpp-rs/src/client/fetch.rs:1 - Avoid .unwrap() in library code. Use proper error handling with ? or .expect() with context.
  • /home/runner/work/mpp-rs/mpp-rs/src/client/fetch.rs:1 - Avoid .unwrap() in library code. Use proper error handling with ? or .expect() with context.
  • /home/runner/work/mpp-rs/mpp-rs/src/client/fetch.rs:1 - Avoid .unwrap() in library code. Use proper error handling with ? or .expect() with context.

... and 686 more

no-leading-whitespace-strings (10 occurrences)
  • /home/runner/work/mpp-rs/mpp-rs/src/protocol/core/headers.rs:1 - String literals should not start with leading whitespace. Found: ' now'
  • /home/runner/work/mpp-rs/mpp-rs/src/protocol/core/headers.rs:1 - String literals should not start with leading whitespace. Found: ' now'
  • /home/runner/work/mpp-rs/mpp-rs/src/protocol/core/headers.rs:1 - String literals should not start with leading whitespace. Found: ' now'
  • /home/runner/work/mpp-rs/mpp-rs/src/protocol/core/types.rs:1 - String literals should not start with leading whitespace. Found: ' and backslashes '
  • /home/runner/work/mpp-rs/mpp-rs/src/proxy/service.rs:1 - String literals should not start with leading whitespace. Found: ' — '
  • /home/runner/work/mpp-rs/mpp-rs/src/error.rs:1 - String literals should not start with leading whitespace. Found: ' for '
  • /home/runner/work/mpp-rs/mpp-rs/src/error.rs:1 - String literals should not start with leading whitespace. Found: ' ({})'
  • /home/runner/work/mpp-rs/mpp-rs/src/error.rs:1 - String literals should not start with leading whitespace. Found: ' is invalid.'
  • /home/runner/work/mpp-rs/mpp-rs/src/error.rs:1 - String literals should not start with leading whitespace. Found: ' is invalid: already used.'
  • /home/runner/work/mpp-rs/mpp-rs/src/error.rs:1 - String literals should not start with leading whitespace. Found: ' (Premium access).'
unsafe-needs-safety-comment (4 occurrences)
  • /home/runner/work/mpp-rs/mpp-rs/src/server/mpp.rs:1 - Unsafe block requires a SAFETY comment explaining why this is safe.
  • /home/runner/work/mpp-rs/mpp-rs/src/server/mpp.rs:1 - Unsafe block requires a SAFETY comment explaining why this is safe.
  • /home/runner/work/mpp-rs/mpp-rs/src/server/mpp.rs:1 - Unsafe block requires a SAFETY comment explaining why this is safe.
  • /home/runner/work/mpp-rs/mpp-rs/src/server/mpp.rs:1 - Unsafe block requires a SAFETY comment explaining why this is safe.

Issues by File

View grouped by file

/home/runner/work/mpp-rs/mpp-rs/src/protocol/core/challenge.rs (85 issues)

  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • ... and 80 more

/home/runner/work/mpp-rs/mpp-rs/src/client/tempo/session/mod.rs (58 issues)

  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • ... and 53 more

/home/runner/work/mpp-rs/mpp-rs/src/client/tempo/signing/mod.rs (54 issues)

  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • ... and 49 more

/home/runner/work/mpp-rs/mpp-rs/src/server/mpp.rs (53 issues)

  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • ... and 48 more

/home/runner/work/mpp-rs/mpp-rs/src/protocol/methods/tempo/session_method.rs (39 issues)

  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • ... and 34 more

/home/runner/work/mpp-rs/mpp-rs/src/client/tempo/session/channel_ops.rs (38 issues)

  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • ... and 33 more

/home/runner/work/mpp-rs/mpp-rs/src/protocol/methods/tempo/method.rs (35 issues)

  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • ... and 30 more

/home/runner/work/mpp-rs/mpp-rs/src/protocol/core/headers.rs (34 issues)

  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • ... and 29 more

/home/runner/work/mpp-rs/mpp-rs/src/server/middleware.rs (27 issues)

  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • ... and 22 more

/home/runner/work/mpp-rs/mpp-rs/src/mcp.rs (25 issues)

  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • Line 1: [warning] no-unwrap-in-lib
  • ... and 20 more

Showing 10 of 45 files


Posted by https://github.com/tempoxyz/lints

Foundation for WebSocket support (TOOLS-323). Adds transport traits
matching mppx's Transport interface:

- server::transport::Transport — get_credential, respond_challenge,
  respond_receipt with associated Input/Output types
- server::transport::HttpTransport — HTTP impl using http crate types
- client::transport::Transport — is_payment_required, get_challenge,
  set_credential with associated Request/Response types
- client::transport::HttpTransport — reqwest impl

Existing HTTP logic (fetch.rs, axum.rs, middleware.rs) is unchanged;
the traits provide a parallel abstraction that WebSocket/MCP transports
will implement in follow-up PRs.

Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com>
@decofe decofe force-pushed the georgen/transport-trait branch from 8a4d1e4 to aceb75d Compare March 24, 2026 07:49
decofe and others added 2 commits March 24, 2026 07:54
Server-side WS transport implementing the Transport trait with JSON
message protocol:

- WsMessage (client→server): credential, data variants
- WsResponse (server→client): challenge, message, needVoucher,
  receipt, error variants
- WsTransport implements get_credential, respond_challenge,
  respond_receipt for WS frames
- ws feature flag gating tokio-tungstenite + futures-util deps

Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com>
Client-side WS transport implementing the Transport trait:

- WsClientMessage (client→server): credential, data variants
- WsServerMessage (server→client): challenge, message, needVoucher,
  receipt, error variants (mirrors server::ws types)
- WsTransport implements is_payment_required, get_challenge,
  set_credential for WS frames

Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com>
@decofe decofe force-pushed the georgen/transport-trait branch from db91654 to 808989b Compare March 24, 2026 07:58
decofe and others added 3 commits March 24, 2026 08:22
axum_ws module handles the full WS payment flow:
1. Client connects → server sends challenge frame
2. Client sends credential → server verifies
3. On success, streams application data as message frames
4. Sends final receipt frame before close

Uses axum dev-dependency with ws feature. Gated behind
cfg(test, ws, axum) until axum becomes a proper optional dep.

Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com>
E2e tests over real WebSocket connections:
- Full challenge → credential → data/error flow
- Message type serialization over the wire
- NeedVoucher roundtrip

Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com>
examples/ws/ with server + client binaries:
- ws-server: axum WS endpoint that sends challenge, verifies
  credential, streams fortunes, sends receipt
- ws-client: tokio-tungstenite client handling the full flow

Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com>
@decofe decofe changed the title feat: add Transport trait abstraction for server and client feat: add WebSocket transport support Mar 24, 2026
Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com>
@decofe decofe force-pushed the georgen/transport-trait branch from 5c252a2 to 218a77c Compare March 24, 2026 08:37
decofe and others added 6 commits March 24, 2026 08:41
Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com>
ws_session module implements the SSE-equivalent metering loop over WS:
- ws_session(): deducts tick_cost per value, sends needVoucher when
  exhausted, waits for channel store update, resumes, emits session
  receipt on completion
- process_incoming_vouchers(): runs on the receiver half, parses
  voucher credentials from WS frames, calls verify_session() which
  updates ChannelStore and wakes the sender

Usage: split WS into sender/receiver, run ws_session +
process_incoming_vouchers concurrently via tokio::select!/join!.

Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com>
- HttpTransport::get_credential() no longer double-prepends 'Payment '
  (extract_payment_scheme already returns the full fragment)
- axum_ws handler now verifies credential.challenge.id matches the
  issued challenge before calling verify_payment (prevents replay)
- Fix doc: need-voucher → needVoucher (matches serde camelCase output)
- Add test for valid Payment header parsing

Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com>
- test_ws_challenge_id_mismatch_rejected: credential with wrong
  challenge ID is rejected (never succeeds as data/receipt)
- test_server_client_wire_type_compat: serialize with server types,
  deserialize with client types and vice versa (all 7 message variants)

Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com>
@grandizzy grandizzy force-pushed the georgen/transport-trait branch from 9cec0af to c39a0fc Compare March 24, 2026 10:22
@grandizzy grandizzy marked this pull request as ready for review March 24, 2026 11:22
@grandizzy grandizzy requested a review from brendanjryan as a code owner March 24, 2026 11:22
Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019d23a5-8821-747b-bfa0-9ae8abc12fa6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants