schollz/croc · error · Error

Peer message did not include a type

Error message

Peer message did not include a type

What it means

decodeMessage() decrypts, decompresses, and JSON-parses a peer message, then requires the parsed wire object to carry a truthy 't' (type) field. Every well-formed croc protocol message must be tagged with a type so the receiver can dispatch it. If the JSON decodes but 't' is absent, null, or empty, the message is considered malformed and rejected.

Solutions

  1. Check what the peer actually sent: log the decoded JSON (before the throw) and confirm the message shape; well-formed croc messages always include a type such as 'pake', 'offer', or chunk data.
  2. Verify both peers run compatible protocol versions; a peer omitting 't' is violating the wire contract and should be updated.
  3. In tests, always include the 't' field when constructing WireMessage fixtures (e.g. { t: "transfer", v: 1 }).
  4. If you control the sender, add the type tag before serializing/encrypting with encodeMessage.

Example fix

// before (test fixture / peer)
const wire = { v: 1, b: base64 }; // missing type
// after
const wire = { t: "transfer", v: 1, b: base64 };
Defensive patterns

Strategy: try-catch

Validate before calling

function looksLikeTypedWire(json: string): boolean {
  try {
    const parsed = JSON.parse(json);
    return typeof parsed?.t === "string" && parsed.t.length > 0;
  } catch {
    return false;
  }
}

Type guard

function isWireMessage(value: unknown): value is { t: string } {
  return typeof value === "object" && value !== null &&
    typeof (value as { t?: unknown }).t === "string" &&
    (value as { t: string }).t.length > 0;
}

Try / catch

try {
  const msg = await decodeMessage(wasm, payload, key);
  dispatch(msg);
} catch (error) {
  if (error instanceof Error && error.message === "Peer message did not include a type") {
    // Malformed peer message: log and drop it, keep the connection alive
    console.warn("dropping untyped peer message");
    return;
  }
  throw error;
}

Prevention

When it happens

Trigger: Calling decodeMessage(wasm, payload, key) where payload decrypts/decompresses to valid JSON without a 't' property (e.g. {"v":1} or an unrelated JSON object). Happens when a peer sends an untyped message, a sender/receiver version mismatch changes the wire schema, or a test feeds hand-crafted JSON through the codec.

Common situations: Interop testing between the browser client and a non-standard/modified croc peer; protocol version drift after upgrading one side; unit tests that build fake peer messages and forget the 't' field; replaying captured frames with a wrong key that happens to yield valid JSON.

Related errors


AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15). Data as JSON: /api/errors/f1d1d69f8ff7c884. Report an issue: GitHub.

Appendix: source

Thrown at web/src/protocol/codec.ts:45

  if (message.m) wire.m = message.m;
  if (message.b?.byteLength) wire.b = bytesToBase64(message.b);
  if (message.b2?.byteLength) wire.b2 = bytesToBase64(message.b2);
  if (message.n) wire.n = message.n;
  let bytes = await wasm.compress(textEncoder.encode(JSON.stringify(wire)));
  if (key) bytes = await wasm.encrypt(bytes, key);
  return bytes;
}

export async function decodeMessage(
  wasm: CrocWasm,
  payload: Uint8Array,
  key?: Uint8Array,
) {
  let bytes = payload;
  if (key) bytes = await wasm.decrypt(bytes, key);
  bytes = await wasm.decompress(bytes, MAX_FRAME_SIZE);
  const wire = JSON.parse(textDecoder.decode(bytes)) as WireMessage;
  if (!wire.t) throw new Error("Peer message did not include a type");
  return {
    t: wire.t,
    v: wire.v,
    m: wire.m,
    b: wire.b ? base64ToBytes(wire.b) : undefined,
    b2: wire.b2 ? base64ToBytes(wire.b2) : undefined,
    n: wire.n,
  } satisfies CrocMessage;
}

View on GitHub (pinned to e25f1bdc04)