Skip to content

Transport

The transport layer handles how messages are sent and received between clients and servers. Teleportal’s transport system is built on the Web Streams API, making it composable and allowing multiple transports to be chained together.

A Transport is a combination of a Source (for reading messages) and a Sink (for writing messages). The server’s perspective is:

{
readable: messagesFromClient,
writable: messagesToClient
}

The client’s perspective is:

{
readable: messagesFromServer,
writable: messagesToServer
}

The transport system follows a composable architecture:

  1. Base transports handle the actual communication (HTTP, SSE, WebSocket, PubSub)
  2. Middleware transports wrap other transports to add functionality (encryption, rate limiting, logging, validation)
  3. Utility functions help compose, pipe, and transform transports

Throttles messages using a token bucket algorithm:

import { withRateLimit } from "teleportal/transports/rate-limiter";
const rateLimitedTransport = withRateLimit(transport, {
maxMessages: 100,
windowMs: 1000,
trackBy: "user",
});

Wraps a transport with end-to-end encryption:

import { getEncryptedTransport } from "teleportal/transports/encrypted";
const encryptedTransport = getEncryptedTransport(handler);

Adds authorization checks to message reading and writing:

import { withMessageValidator } from "teleportal/transports/message-validator";
const validatedTransport = withMessageValidator(transport, {
isAuthorized: async (message, type) => {
// Your authorization logic
return true;
},
});

Logs all messages for debugging:

import { withLogger } from "teleportal/transports/logger";
const loggedTransport = withLogger(transport);

Adds acknowledgment message support for reliable message delivery:

import { withAckSink, withAckTrackingSink } from "teleportal/transports/ack";
// Server: Send ACKs automatically
const ackSink = withAckSink(sink, {
pubSub,
ackTopic: "acks",
sourceId: "server-1",
});
// Client: Track ACKs
const trackingSink = withAckTrackingSink(sink, {
pubSub,
ackTopic: "acks",
sourceId: "client-1",
ackTimeout: 10000,
});

Transports can be composed in layers:

// Base transport
let transport = getBaseTransport();
// Add encryption
transport = getEncryptedTransport(handler);
// Add rate limiting
transport = withRateLimit(transport, options);
// Add logging
transport = withLogger(transport);
// Add message validation
transport = withMessageValidator(transport, {
isAuthorized: async (message, type) => {
// Authorization logic
return true;
},
});

The YDoc transport integrates Y.js documents with the Teleportal transport system:

import { getYTransportFromYDoc } from "teleportal/transports/ydoc";
const transport = getYTransportFromYDoc({
ydoc,
awareness,
document: "my-document",
context: { clientId: "client-1" },
});
// Start synchronization
await transport.handler.start();

Combines a Source and Sink into a Transport:

import { compose } from "teleportal/transports";
const transport = compose(source, sink);

Pipes messages from a Source to a Sink:

import { pipe } from "teleportal/transports";
await pipe(source, sink);

Bidirectionally syncs two transports:

import { sync } from "teleportal/transports";
await sync(transportA, transportB);
  1. Compose from bottom up: Start with base transports, then add middleware
  2. Handle errors: Transports can error, ensure proper error handling
  3. Clean up resources: Call close() or unsubscribe() when done
  4. Use appropriate transports: Choose transports based on your use case
  5. Rate limit client transports: Always rate limit client-side transports to prevent abuse
  6. Validate messages: Use message validators for authorization checks
  7. Monitor with logging: Use logger transport during development