Skip to content

Examples

The examples folder holds a small site with six pictures. Serve it from a clone of the repository:

Terminal window
npm ci
npm run examples

Open http://localhost:5173 in one browser, then in a second browser, or a private window, while the first stays open. The second gets the pictures from the first over WebRTC, and the count of pictures the server sent stays the same. Tabs of one browser share its cache, so they never need a peer.

Pictures come from the cache, then from the other visitors, then from the server:

examples/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();

The page registers the worker, runs PeerPlugin, and shows the pictures once it has found a peer, or after three seconds:

examples/main.ts
/// <reference types="vite/client" />
import { io } from 'socket.io-client';
import { PeerPlugin, SocketChannel } from 'peer-cdn';
const PICTURES = ['dunes', 'tide', 'orbit', 'mosaic', 'bloom', 'peaks'];
const status = document.querySelector('#status');
const gallery = document.querySelector('#gallery');
const served = document.querySelector('#served');
let foundPeer: () => void = () => undefined;
const firstPeer = new Promise<void>((resolve) => (foundPeer = resolve));
// The page side: it finds pictures among the other visitors for the worker, and shares this browser's cache.
new PeerPlugin({
signaling: new SocketChannel(io()),
onPeers: (count) => {
if (status) status.textContent = `Connected to ${String(count)} other ${count === 1 ? 'visitor' : 'visitors'}.`;
if (count > 0) foundPeer();
},
});
// The Vite plugin serves sw.ts, bundled, as sw.js.
await navigator.serviceWorker.register(`${import.meta.env.BASE_URL}sw.js`);
if (!navigator.serviceWorker.controller) {
await new Promise((resolve) => {
navigator.serviceWorker.addEventListener('controllerchange', resolve, { once: true });
});
}
// A peer can only send pictures once it's connected: wait a moment for one, as a page that loads its pictures
// later would.
await Promise.race([firstPeer, new Promise((resolve) => setTimeout(resolve, 3000))]);
const loaded = PICTURES.map((name) => {
const picture = new Image(480, 320);
picture.alt = name;
picture.src = `/assets/${name}.svg`;
gallery?.append(picture);
return picture.decode().catch(() => undefined);
});
await Promise.all(loaded);
const counts = (await (await fetch('/served')).json()) as { served: number };
if (served) served.textContent = `The server has sent ${String(counts.served)} pictures since it started.`;

The Vite plugin serves the worker and runs the signaling server on the same port, and a second plugin counts the pictures the server sends. It imports the plugin from the repository’s source; your config imports it from peer-cdn/vite:

examples/vite.config.ts
import { fileURLToPath } from 'node:url';
import { defineConfig } from 'vite';
import { peerCDN } from '../src/vite.js';
// npm run examples: the example site on one dev server, with its service worker and signaling server.
export default defineConfig({
root: fileURLToPath(new URL('.', import.meta.url)),
resolve: { alias: { 'peer-cdn': fileURLToPath(new URL('../src/index.ts', import.meta.url)) } },
plugins: [
peerCDN({ worker: 'sw.ts' }),
{
// Counts the pictures the server sends, so the page can show that a second browser didn't need them.
name: 'served-counter',
configureServer(server) {
let served = 0;
server.middlewares.use('/assets/', (_request, _response, next) => {
served++;
next();
});
server.middlewares.use('/served', (_request, response) => {
response.setHeader('Content-Type', 'application/json');
response.setHeader('Cache-Control', 'no-store');
response.end(JSON.stringify({ served }));
});
},
},
],
});

examples/visitors/visitor.ts is a visitor without a service worker, which the live demo runs twice in one page. Its tests run it in Chromium, Firefox and WebKit.