Skip to main content

Network Interception

Network interceptors allow you to hook into the message flow between client and server. Any object with an onMessage method can be registered as an interceptor.

The NetIntercept Interface

Interceptors implement the NetIntercept interface:

NetIntercept Interface
interface NetIntercept {
onMessage?: (message: MessageProtocol) => void;
packets?: any[];
}
  • onMessage - Called when any message is received from the server
  • packets - Array of outgoing packets to send on the next flush

Registering Interceptors

Register interceptors with the network:

Registering Interceptors
const network = new VOXELIZE.Network();

network.register(world);
network.register(peers);
network.register(entities);
network.register(myCustomInterceptor);

Built-in interceptors:

  • World - Handles chunk loading, world updates, initialization
  • Peers - Handles peer position and metadata updates
  • Entities - Handles entity creation, updates, and deletion
  • Events - Handles custom event messages
  • Method - Handles method call responses

Creating a Custom Interceptor

Custom Interceptor
import { MessageProtocol } from "@voxelize/protocol";

const chatLogger = {
onMessage(message: MessageProtocol) {
if (message.type === "CHAT" && message.chat) {
console.log(`[${message.chat.sender}]: ${message.chat.body}`);
}
},
};

network.register(chatLogger);

Message Types

Common message types you can intercept:

TypeDescription
INITInitial world data (config, blocks, peers, entities)
JOINPlayer joined the world
LEAVEPlayer left the world
PEERPeer position/metadata update
ENTITYEntity create/update/delete
LOADChunk data received
UPDATEWorld stats update (time, etc.)
CHATChat message
EVENTCustom event
METHODMethod call response

Sending Packets

Interceptors can queue outgoing packets:

Sending Packets
const myInterceptor = {
packets: [] as any[],

sendChat(message: string) {
this.packets.push({
type: "CHAT",
chat: {
sender: "Player",
body: message,
},
});
},

onMessage(message: MessageProtocol) {
// Handle incoming messages
},
};

network.register(myInterceptor);

// Later, send a chat message
myInterceptor.sendChat("Hello world!");

Packets are automatically flushed each frame by the network.

Example: Entity Counter

Track entity counts by type:

Entity Counter
import { MessageProtocol } from "@voxelize/protocol";

class EntityCounter {
counts: Map<string, number> = new Map();

onMessage = (message: MessageProtocol) => {
if (!message.entities) return;

for (const entity of message.entities) {
const current = this.counts.get(entity.type) || 0;

switch (entity.operation) {
case "CREATE":
this.counts.set(entity.type, current + 1);
break;
case "DELETE":
this.counts.set(entity.type, Math.max(0, current - 1));
break;
}
}
};

getCount(type: string): number {
return this.counts.get(type) || 0;
}
}

const counter = new EntityCounter();
network.register(counter);

Example: Latency Monitor

Measure round-trip time:

Latency Monitor
class LatencyMonitor {
packets: any[] = [];
private pingTime = 0;
latency = 0;

ping() {
this.pingTime = performance.now();
this.packets.push({
type: "METHOD",
method: { name: "ping", payload: "{}" },
});
}

onMessage = (message: MessageProtocol) => {
if (message.type === "METHOD" && message.method?.name === "pong") {
this.latency = performance.now() - this.pingTime;
}
};
}

Order of Execution

When a message arrives:

  1. network.sync() is called (automatically each frame)
  2. Message is decoded from the WebSocket
  3. Each registered interceptor's onMessage is called in registration order
  4. Interceptors process the message

When sending:

  1. Interceptors add packets to their packets array
  2. network.flush() is called (automatically each frame)
  3. All packets from all interceptors are collected and sent
  4. packets arrays are cleared

Read on to learn about collision detection.