Skip to content

Getting started

A site with peer-cdn has three parts:

  • The service worker gets your pages’ requests for assets and routes them through middleware: this browser’s cache, then the other visitors, then your server.
  • Each page runs PeerPlugin, which connects to the other visitors’ pages over WebRTC. Service workers can’t use WebRTC, so the worker asks its page to find files among the visitors, and the page shares what the worker cached.
  • A signaling server introduces the pages to each other. After that, files go straight from one browser to another.
Terminal window
npm install peer-cdn socket.io-client

peer-cdn is an ES module with types, and installs peer-data, which holds the pages’ connections. It passes on peer-data’s SocketChannel, so pages import everything from peer-cdn. The signaling server needs socket.io on Node.js: npm install socket.io where it runs.

This worker answers requests for everything under assets/ next to it, from the cache, the visitors, then the server:

sw.ts
import { CachePlugin, DelegatePlugin, NetworkPlugin, PeerCDN, ordered } from 'peer-cdn';
// The folder this worker is served from, which is its scope: the site's pages and assets are below it.
const base = new URL('./', location.href).pathname;
const cache = new CachePlugin();
const peers = new DelegatePlugin();
const network = new NetworkPlugin();
// Pictures come from this browser's cache, then from other visitors, then from the server. What the peers or
// the server send is cached on the way, for this visitor and for the peers it shares with.
new PeerCDN().GET(`${base}assets`, ordered, cache.getMiddleware, peers.getMiddleware, network.getMiddleware).register();

ordered asks each middleware in turn. The ones asked before the one that answered get a copy of the response, so what came from the visitors or the server is cached, and this visitor shares it in turn. Routes and strategies covers the rest.

Browsers run a service worker from one script, so bundle the worker before serving it. With Vite, the Vite plugin does that for vite and vite build:

vite.config.ts
import { defineConfig } from 'vite';
import { peerCDN } from 'peer-cdn/vite';
export default defineConfig({
plugins: [peerCDN({ worker: 'src/sw.ts' })],
});

With another bundler, build the worker on its own into one classic script, such as an IIFE, and serve it from the top folder of what it should control: a service worker only controls pages at or below its own path.

main.ts
import { io } from 'socket.io-client';
import { PeerPlugin, SocketChannel } from 'peer-cdn';
new PeerPlugin({ signaling: new SocketChannel(io('https://signaling.example.com')) });
await navigator.serviceWorker.register('/sw.js');

PeerPlugin answers the worker by itself. Create it on every page the worker controls, before the page loads the files you route, so it can answer the first requests. Only the worker classes end up in the worker’s bundle: PeerPlugin and peer-data stay in the page’s.

server.ts
import { createServer } from 'node:http';
import { attachSignaling } from 'peer-cdn/server';
const server = createServer();
attachSignaling(server, { cors: { origin: 'https://www.example.com' } });
server.listen(8080);

While you develop with Vite, the plugin runs it on Vite’s own server, and pages reach it with io(). Signaling covers ICE servers and signaling of your own.

  1. Clone the repository and serve the example site:

    Terminal window
    git clone https://github.com/vardius/peer-cdn.git
    cd peer-cdn
    npm ci
    npm run examples
  2. Open http://localhost:5173 in one browser. It loads six pictures from the server, and its worker caches them.

  3. Open the same address in another browser, such as a private window. It finds the first browser and gets the pictures from it: the page shows how many pictures the server has sent, which stays at six. To try another device on your network, serve the example with npm run examples -- --host.

Or try it right here, in the live demo.