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.

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)

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.