DeutschGitHub
prototypepackages/p2p/src

P2P

@ralphschuler/p2p

Starts TCP peers explicitly and transports bounded, length-prefixed frames.

peer-to-peerframingdiscovery

01 · Problem

What is it for?

A peer coordinator must own sockets and discovery records throughout its lifecycle.

02 · Mental model

How should you think about it?

Separate address discovery, byte transport, message framing and authentication into explicit contracts.

In this repository

Starts TCP peers explicitly and transports bounded, length-prefixed frames. Local discovery is the default; NetworkMDNS can be injected with an advertised network hostname. TCP data is unencrypted; trust establishment, authentication and replay protection need a full protocol.

03 · Control flow

What happens in which order?

  1. Start a TCP listener explicitly.
  2. Announce its actual port.
  3. Discover service records.
  4. Create peer connection state.
  5. Close peers, listener and discovery records on destroy.

04 · Building blocks

The important contracts

P2P.start / discoverPeers / destroyOwn the TCP server and discovery lifecycle.
Peer.connect / send / disconnectTransport bounded plaintext frames over TCP.
KeyPairEncryptionDemonstrate RSA encryption and signing as separate primitives.

05 · Build it yourself

Implement a small core

Start with this small independent implementation, then use the steps below to make its contract explicit.

  1. Start a TCP listener explicitly.
  2. Announce its actual port.
  3. Discover service records.
  4. Create peer connection state.
  5. Close peers, listener and discovery records on destroy.
Concept sketch · may reference omitted helpers
type Frame = { type: number; sequence: number; payload: Uint8Array };

function encodeFrame(frame: Frame) {
  const body = encodeBody(frame);
  if (body.length > 1_048_576) throw new RangeError("Frame too large");
  const output = new Uint8Array(4 + body.length);
  new DataView(output.buffer).setUint32(0, body.length);
  output.set(body, 4);
  return output;
}

function acceptSequence(last: number, next: number) {
  if (next !== last + 1) throw new Error("Invalid sequence");
}

06 · Verify

What should you test?

  • Start on an ephemeral port and verify the announcement.
  • Ensure destroy removes its discovery record.
  • Test split messages, disconnections and shutdown with active sockets.

Try it yourself

Turn the first verification claim into a failing test, implement the smallest fix, then add a boundary case.

Start on an ephemeral port and verify the announcement.

Compare your approach

Your check should fail when the stated invariant is deliberately broken, and pass again after the fix. Keep setup deterministic and assert an observable result.

07 · Limits

Trade-offs and failure modes

  • Real multicast transport remains incomplete.
  • TCP needs explicit framing.
  • Authentication and replay protection require a complete protocol.
Important

Local discovery is the default; NetworkMDNS can be injected with an advertised network hostname. TCP data is unencrypted; trust establishment, authentication and replay protection need a full protocol.

08 · Keep exploring

Source code and related ideas

yarn tsx packages/p2p/examples/basic.ts