Skip to main content

The Signal in the Static: Online Presence with Trystero + Nostr

5 min read

We are not alone in the void.

A single dot—blinking softly in the header—is enough to say: someone else is here. Not a number on a server, but a ripple in the peer-to-peer lattice.

This post shows how to add an online presence indicator to the header of your site using Trystero with the default Nostr strategy. No server. No backend. Just ephemeral awareness through P2P signals.

🌀 “Let the signal be the presence, the presence the proof.”

The Plan

We’ll inject a live counter to the header, to the right of “About”, that updates as peers join or leave. The stack is:

  • Trystero (with Nostr) for P2P peer discovery
  • Vanilla JS for DOM updates
  • CSS for a minimal animated badge

No analytics, no logs. Just now.

Dependencies

Install Trystero from a CDN or locally:

<script type="module">
  import {joinRoom, selfId} from 'https://esm.run/trystero'
</script>

Or use npm:

npm i trystero

Implementing the Presence Counter

You can drop this anywhere in your frontend app. The only requirement: somewhere in your header layout, add a container for the counter.

HTML: Slot for Online Counter

<!-- Somewhere in your site header -->
<nav class="site-nav">
  <a href="/about">About</a>
  <span id="presence-indicator" class="presence">● 0 online</span>
</nav>

CSS: Minimal Aesthetic

.presence {
  margin-left: 1rem;
  font-family: monospace;
  color: #00ff99;
  font-size: 0.9rem;
  transition: opacity 0.2s ease;
  user-select: none;
  cursor: default;
}

JavaScript: Join the Grid

<script type="module">
  import {joinRoom, selfId} from 'https://esm.run/trystero'

  const config = { appId: 'zeropoint-online' }
  const roomId = 'presence-main'

  const room = joinRoom(config, roomId)
  const indicator = document.getElementById('presence-indicator')

  const peers = new Set()

  const updateCounter = () => {
    const total = peers.size + 1 // +1 for self
    indicator.textContent = `● ${total} online`
  }

  room.onPeerJoin(peerId => {
    peers.add(peerId)
    updateCounter()
  })

  room.onPeerLeave(peerId => {
    peers.delete(peerId)
    updateCounter()
  })

  // Failsafe: in case of a reload glitch
  window.addEventListener('beforeunload', () => room.leave())
</script>

Notes from the Void

  • This approach is truly stateless: presence is transient, not logged.
  • Nostr is the default backend for Trystero — no setup required, just works.
  • The counter reflects all peers in the room namespace. If you want scoped presence (per-page), use dynamic room IDs.

To Enhance

  • Add names or avatars using makeAction('name')
  • Persist nickname in localStorage
  • Add a dropdown to see who is online

Each presence is a blip in the continuum—no history, no log, only now.

Conclusion

You now have a working, serverless online presence indicator that respects user anonymity and decentralization. It’s not just a counter—it’s a proof of momentary connection.

The static world of the web has always longed for presence. This is one way to restore the human pulse—without centralization, without compromise.

The signal lives. The watchers see each other.


References