Peerix v0.5 Released
Peerix v0.5 is now available. This is a significant update that reimagines how data travels between peers — introducing streaming transfers with progress tracking, abortable transmissions, and optional metadata — while simplifying the underlying channel architecture.
Highlights: Streaming Data Transfers
v0.5 fundamentally changes how send works and how you receive data over channels. Instead of a fire-and-forget API, send now returns an async iterable that doubles as a promise — letting you track transfer progress or simply await completion.
ReadableStream for incoming and outgoing data
The send method now supports streaming any data through a single channel. Large payloads are automatically chunked and buffered on the wire, so you can transmit files and binary blobs without worrying about maximum message size limits.
On the receiving side, the channel:message event fires as soon as the first chunk arrives. The data payload is a ReadableStream/Promise that you can iterate over to receive chunks incrementally — or consume as a promise to get the fully assembled message when you don’t need progress tracking.
Progress tracking
The transfer object returned by send is an async iterator. By looping over it, you can observe how much data has been sent at each step:
const file = new File([new Uint8Array(1024 * 1024)], "example.dat");
const transfer = peer.send(file, {
label: "chat", // channel label
info: { name: file.name, size: file.size }, // metadata
signal: AbortSignal.timeout(10000), // abort signal
});
// track the progress of the transfer
for await (const progress of transfer) {
const { id, label, current, total } = progress;
const percent = Math.round((current / total) * 100);
console.log(`[${id}:${label}] Sending... ${percent}%`);
}Receiving the file and tracking progress on the other end:
peer.on("channel:message", async (e) => {
const { remote, label, data, info } = e;
let current = 0;
const chunks = [];
// read data by chunks
for await (const chunk of data) {
chunks.push(chunk);
current += chunk.length;
const percent = Math.round((current / info.size) * 100);
console.log(`[${remote.id}:${label}] Receiving... ${percent}%`);
}
const file = new File(chunks, info.name);
console.log("Received:", file);
// cancel the stream if needed
// data.cancel();
});Abortable transfers
You can cancel a transmission at any time using AbortSignal. The example above uses AbortSignal.timeout(10000) to auto-cancel after 10 seconds, but you can also create an AbortController and call .abort() manually whenever your application logic dictates.
Cancellation works on the receiver side as well. Since incoming data is a ReadableStream, you can call data.cancel() at any point during iteration to stop receiving further chunks.
Optional Metadata
The new info option on send lets you attach arbitrary metadata alongside your data. This metadata is delivered with every channel:message event, so the receiving peer can inspect it without waiting for the full payload. This is useful for describing the payload — file names, sizes, types, or any custom information the receiver needs to process the message correctly.
Breaking changes
- Data is now sent through a single data channel per peer. If you omit the
labeloption, messages are routed to thedefaultchannel instead of all existing channels. - Incoming data is now either a
ReadableStreamor aPromise. Read the stream to track receiving progress or use the promise to get a specific data type when the full message is received.
Peer Serialization
A new toJSON method was added to both local and remote peers. Use it to serialize peer identity information for logging, debugging, or state persistence:
console.log(peer.toJSON()); // { id: "...", room: "...", ... }
Testing Improvements
Unit tests were added for internal utilities and several signaling drivers. This improves confidence in core functionality and makes future refactoring safer.
Upgrade Notes
When upgrading to v0.5, review the following breaking changes:
sendAPI: The return value is now an async iterable transfer object instead of a plain Promise. If you previously awaitedsenddirectly, it will still work (the transfer resolves when delivery completes), but you can now also iterate over it for progress updates.- Incoming data shape: The
datainchannel:messageevents is now always aReadableStream/Promise. To receive the full message, await it:const payload = await event.data. To track receiving progress, iterate withfor await (const chunk of event.data). - Single channel: Data is sent over one data channel instead of multiple. The
defaultlabel is used when nolabelis provided.
Conclusion
v0.5 transforms data channel messaging into a first-class streaming experience with progress visibility and transfer control built in. For full details on these changes, please visit our updated resources:
