Skip to content

Live demo

Everything below runs in your browser. On a real site, each visitor is a different browser and peer-cdn’s service worker does the loading. Here, two visitors share this page, each with a cache of its own, and load the pictures the way the worker would: from their cache, then from the other visitors, then from the server.

Your server has sent 0 pictures.

Visitor 1

Looking for other visitors…

  • DunesNot loaded
  • TideNot loaded
  • OrbitNot loaded
  • MosaicNot loaded
  • BloomNot loaded
  • PeaksNot loaded

Visitor 2

Looking for other visitors…

  • DunesNot loaded
  • TideNot loaded
  • OrbitNot loaded
  • MosaicNot loaded
  • BloomNot loaded
  • PeaksNot loaded
  1. Load the pictures as Visitor 1. No one has them yet, so they come from the server, which this site is served from. Visitor 1 keeps them in its cache.
  2. Load them as Visitor 2. Visitor 1 has them, so they come from Visitor 1 over a WebRTC data channel, and the server’s count stays the same.
  3. Load them again as either visitor. They come from its own cache.
  4. Clear Visitor 1’s cache, then load. Visitor 2 sends them back.

Open this page in another tab of the same browser and its visitors join in too.

Each visitor runs PeerPlugin, the part of peer-cdn that runs in pages, with a cache name of its own. They find each other through peer-data’s BroadcastChannelSignaling, which reaches the pages of one browser with no server. load stands in for the service worker with its CachePlugin, DelegatePlugin and NetworkPlugin:

examples/visitors/visitor.ts
import { PeerPlugin, type PeerPluginOptions } from 'peer-cdn';
/** Where a visitor got a file from. */
export type Source = 'cache' | 'peers' | 'server';
export interface Loaded {
response: Response;
from: Source;
}
/**
* A visitor of a site, in one page: it loads files as peer-cdn's worker would, from its cache, then from the
* other visitors, then from the server, keeping what it loads, and shares its cache with the other visitors.
* The site's demo runs two in one page, each with a cache of its own, where a real site has one per browser.
*/
export function visit(options: PeerPluginOptions & { cacheName: string }) {
const { cacheName } = options;
const plugin = new PeerPlugin(options);
return {
/** Loads url, and says where it came from. */
load: async (url: string): Promise<Loaded> => {
const cache = await caches.open(cacheName);
const cached = await cache.match(url);
if (cached) return { response: cached, from: 'cache' };
const fromPeers = await plugin.fetch(url);
const response = fromPeers ?? (await fetch(url));
if (response.status === 200) await cache.put(url, response.clone());
return { response, from: fromPeers ? 'peers' : 'server' };
},
/** Empties this visitor's cache, as clearing site data would. */
forget: async (): Promise<void> => {
await caches.delete(cacheName);
},
leave: (): void => {
plugin.close();
},
};
}

The pictures are the ones the example site serves. PeerPlugin shares them because they are same-origin 200 responses not marked private or no-store: see what visitors share.

The visitors signal over BroadcastChannel, which reaches pages of the same origin in one browser. To connect different browsers or devices, pages need a signaling server: see Signaling.