Data
Sending
Section titled “Sending”participant.send('hello'); // to one participantroom.send('hello, everyone'); // to every participant in the room nowsend takes a string, an ArrayBuffer, or a typed array or DataView. Send objects as JSON:
room.send(JSON.stringify({ type: 'move', x: 3, y: 5 }));Each participant has one data channel, created with the connection, so it survives renegotiation when media
is added. What you send before it opens waits, and goes out in order once it does: you can send from the
participant listener. What waits is a copy, so changing a buffer after sending it doesn’t change what
arrives.
send refuses a Blob: browsers read a Blob before sending it, so it could arrive before data sent ahead of
it. Send await blob.arrayBuffer() instead.
Sending to a participant that is disconnecting or has disconnected throws. room.send still sends to every
other participant, then throws the first such error.
Receiving
Section titled “Receiving”participant.on('message', (data) => { if (typeof data === 'string') { console.log('text', data); } else { console.log('binary', new Uint8Array(data)); }});Text arrives as a string, and binary data as an ArrayBuffer, in every browser.
Anyone in the room can send anything, so check what arrives before you trust it: the chat example parses each line and drops what isn’t one.
Channel options
Section titled “Channel options”Options for the data channel go in channel, and apply to the channel to every participant:
const peer = new PeerData(signaling, { channel: { ordered: false, maxRetransmits: 0 },});That makes a channel like UDP: messages can arrive out of order or not at all, but a lost one never holds back the next. Every peer in the room must use the same options, because both sides of a connection create the channel. The default is ordered and reliable.
Large data
Section titled “Large data”A message is sent whole, and browsers limit how large one can be: the connection’s
participant.connection.sctp?.maxMessageSize says how large. Split a file into chunks, for example of 16 KiB,
send them in order on a reliable channel, and join them on the other side.
Browsers also limit how much a channel buffers, and send throws past it, so a large file can’t go out in
one loop. participant.channel is the data channel: wait for its bufferedamountlow event whenever its
bufferedAmount is high. This sends 20 MiB in its tests:
import type { Participant } from 'peer-data';
/** The size of message every browser's data channel carries. */const CHUNK = 16 * 1024;/** How much to let the channel buffer before waiting: browsers refuse sends past a limit of their own. */const HIGH_WATER = 1024 * 1024;
/** * Sends bytes to participant in pieces, waiting whenever its channel has buffered enough. Rejects if the * participant disconnects first. */export async function sendLarge(participant: Participant, bytes: Uint8Array<ArrayBuffer>): Promise<void> { const { channel } = participant; if (channel.readyState === 'connecting') await settled(channel, 'open'); channel.bufferedAmountLowThreshold = HIGH_WATER / 2; for (let offset = 0; offset < bytes.byteLength; offset += CHUNK) { if (channel.bufferedAmount > HIGH_WATER) await settled(channel, 'bufferedamountlow'); participant.send(bytes.subarray(offset, offset + CHUNK)); }}
/** Collects the next size bytes participant sends. */export function receiveLarge(participant: Participant, size: number): Promise<Uint8Array<ArrayBuffer>> { const bytes = new Uint8Array(size); let received = 0; return new Promise((resolve) => { const onMessage = (data: string | ArrayBuffer): void => { if (typeof data === 'string') return; bytes.set(new Uint8Array(data), received); received += data.byteLength; if (received < size) return; participant.off('message', onMessage); resolve(bytes); }; participant.on('message', onMessage); });}
/** Resolves once channel fires type, or closes: a send after that throws. */function settled(channel: RTCDataChannel, type: 'open' | 'bufferedamountlow'): Promise<void> { return new Promise((resolve) => { const done = (): void => { channel.removeEventListener(type, done); channel.removeEventListener('close', done); resolve(); }; channel.addEventListener(type, done); channel.addEventListener('close', done); });}