npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lnsy/sync-component

v0.2.0

Published

A simple component for syncing two browsers with WebRTC

Readme

sync-component

A simple component for syncing two browsers with WebRTC

This component is an easy way to sync two web browsers using only an HTML Element.

To use, import the component (see Installation below).

Use like :

  <sync-component></sync-component>

Sync Component has no concept of a "user", and it never will. It should be used and reasoned about as two specific devices and how they will interact.

Installation

  1. Install the Package:

    npm install @lnsy/sync-component
  2. Import the Component: Import the component in your project — it self-registers as the sync-component custom element upon import:

    import '@lnsy/sync-component';

Development

To work on the component itself:

  1. Clone the Repository: Clone or download the repository to your local machine.

  2. Install Dependencies:

    npm install

    This installs peerjs (the only runtime dependency) and Vite (the build tool).

  3. Run the Demo:

    npm run dev

    Open the printed local URL to see <sync-component> in action. The demo page loads the committed bundle build/sync-component.min.js (the same artifact published to npm), so run npm run build after changing src/ to see your changes in the demo.

  4. Build the Library Bundle:

    npm run build

    Outputs a self-contained, minified bundle (with peerjs included) to:

    • build/sync-component.min.js (ES module)

Usage

Include the custom tag in your HTML:

<sync-component></sync-component>

The component initializes itself and connects to the PeerJS signaling server automatically. From there:

  1. It shows a dialog with an 8-character sync code (also exposed as the peer-id attribute, and as a shareable URL in the peer-link attribute).
  2. A second browser connects by entering that code in its own dialog, or by opening the peer-link URL (the ?peer-id= query parameter triggers an automatic connection attempt, with retries on timeout).
  3. Once connected, the element displays a file picker for sending files, and your app can exchange messages through the API and events below.
const sync = document.querySelector('sync-component');

sync.addEventListener('PEER-CONNECTED', () => {
  sync.sendMessage({ text: 'hello from this browser' });
});

sync.addEventListener('PEER-MESSAGE', (e) => {
  console.log('peer says:', e.detail.message);
});

sync.addEventListener('FILE-RECEIVED', (e) => {
  const { blob, name } = e.detail;
  // e.g. offer the blob as a download
});

API

All methods and attributes live on the <sync-component> element instance.

Attributes (set by the component, useful to observe):

  • peer-id — this browser's sync code, available after the server connection opens.
  • peer-link — a shareable URL that auto-connects whoever opens it.

Methods:

  • sendMessage(message) — send any JSON-serializable value to the connected peer. It arrives on the other side as a PEER-MESSAGE event.
  • sendFile(file) — send a Blob/File to the connected peer in checksummed chunks, with progress events and automatic retransmission of missing chunks. Resolves when the peer confirms receipt.
  • connectToPeer(code) — connect to a peer by sync code programmatically (what the dialog and ?peer-id= URL parameter use internally).

Configuration

By default the component uses the public PeerJS cloud server. To use your own signaling server, set window.SYNC_COMPONENT_PEER_CONFIG before the element connects; its contents are passed through as PeerJS options:

<script>
  window.SYNC_COMPONENT_PEER_CONFIG = {
    host: 'localhost',
    port: 9000,
    path: '/'
  };
</script>
<sync-component></sync-component>

Connecting across different networks (TURN servers)

If the host browser reports an incoming peer but the connecting browser times out with "Could not connect to peer", the two browsers could not establish a direct WebRTC path — usually because of NAT traversal (the built-in STUN/TURN servers are blocked or unreachable, e.g. on networks that disallow outbound UDP). The fix is to provide a reachable TURN server via config.iceServers. Your servers are added on top of the PeerJS built-in defaults:

<script>
  window.SYNC_COMPONENT_PEER_CONFIG = {
    config: {
      iceServers: [
        {
          urls: 'turn:turn.example.com:3478',
          username: 'user',
          credential: 'pass'
        }
      ]
    }
  };
</script>
<sync-component></sync-component>

You can run your own TURN server with coturn or use a hosted TURN provider.

STUN servers

The component bundles a list of public STUN servers (src/stun-servers.txt). On every page load it picks one at random and prefers it over the fallback ICE servers, so a single unreachable or defunct public server can't break NAT discovery. (Peers don't need matching STUN servers — each one only uses STUN to discover its own public address — so the pick is not shared between peers.) Edit src/stun-servers.txt (one host:port per line, # comments allowed) and rebuild to customize the pool.

Events Emitted

The component dispatches CustomEvents from the element itself (they do not bubble), with payloads in event.detail:

  • SERVER-CONNECTION-OPEN — connected to the signaling server and ready to accept peers. No detail.
  • PEER-CONNECTED — a data connection with a peer is established. No detail. (May fire more than once per connection.)
  • PEER-MESSAGE — a message arrived from the peer. detail is { message: value }, where value is what the peer passed to sendMessage.
  • PEER-CONNECTION-RETRY — a connection attempt timed out and is being retried. detail: { peerId, attempt }.
  • PEER-CONNECTION-ERROR — connecting to a peer failed (unknown code or repeated timeouts). detail is the error object.
  • FILE-TRANSFER-PROGRESS — progress for an in-flight transfer. When sending, detail: { name, sent, total } (chunk counts; total is 0 while the transfer starts). When receiving, detail: { received, total, transferId }.
  • FILE-TRANSFER-COMPLETE — the peer confirmed receipt of a file you sent. detail: { name, size }.
  • FILE-TRANSFER-ERROR — a transfer failed (timeout or checksum mismatch). detail: { error }.
  • FILE-RECEIVED — a file from the peer was reassembled and verified. detail: { blob, name, mimeType, size }.

These events are dispatched via the dtrmEvent method of the DataroomElement base class, which SyncComponent extends.

Testing

The project has both unit and end-to-end tests:

npm test        # unit tests (Vitest + jsdom)
npm run test:e2e  # end-to-end tests (Playwright)
npm run test:all  # both

The e2e suite starts the Vite dev server and a local PeerJS signaling server (peer package, see e2e/peer-server.mjs) automatically. Tests run two real browser peers that connect and exchange WebRTC messages. The component picks up custom PeerJS options from window.SYNC_COMPONENT_PEER_CONFIG (see Configuration above), which the tests inject via Playwright — no configuration is needed to run them.

Before running e2e tests for the first time, install the browser:

npx playwright install chromium

Customizing SyncComponent

You can extend or modify the SyncComponent class to suit your application-specific needs. For advanced usage, consider overriding methods or adding new functionalities as per your requirements.


Prior Work

Peer JS https://peerjs.com/ Local Forage https://github.com/localForage/localForage