Simple Presence

Core / Vanilla SDK

Full reference for @simple-presence/core — the framework-agnostic presence client.

The @simple-presence/core package is the framework-agnostic client that powers all framework bindings. Use it directly with vanilla JS/TS, Vue, Svelte, Angular, or any other framework.

Installation

npm install @simple-presence/core

SimplePresence class

The main entry point. Instantiate it with a config object and it immediately opens a WebSocket connection and starts receiving count updates.

Constructor

new SimplePresence(config: PresenceConfig)

Config

FieldTypeDescription
tagstringPresence channel identifier
appKeystringYour application key from the dashboard
apiUrlstring?Custom server URL (defaults to hosted service)
onCountChange(count: number) => voidCallback fired on every count update

Basic example

presence.ts
import { SimplePresence } from "@simple-presence/core";

const presence = new SimplePresence({
  tag: "homepage",
  appKey: "your-app-key",
  onCountChange: (count) => {
    document.getElementById("counter")!.textContent = `${count} online`;
  },
});

// Get the current count at any point
console.log("Current count:", presence.getCount());

// Clean up when done (e.g. on page navigation)
presence.destroy();

initPresence helper

An async factory that wraps the constructor. It throws if called outside a browser environment (no window object), making it useful for SSR-safe initialization.

async-init.ts
import { initPresence } from "@simple-presence/core";

const presence = await initPresence({
  tag: "checkout",
  appKey: "your-app-key",
  onCountChange: (count) => {
    console.log("Users on checkout:", count);
  },
});

// Use presence...
presence.destroy();

Instance methods

MethodReturnsDescription
getCount()numberCurrent live count (synchronous)
getStatus()"online" | "away"Current visibility status of this client
getClientId()stringStable anonymous ID for this browser
getHistory()Promise<CountSnapshot[]>Fetch historical count snapshots
getStats()Promise<TagPeak>Fetch peak concurrent count and timestamp
destroy()voidRelease the subscription and close the connection if unused

History & Stats

analytics.ts
import { SimplePresence } from "@simple-presence/core";

const presence = new SimplePresence({
  tag: "product-page",
  appKey: "your-app-key",
});

// Fetch historical count snapshots
const history = await presence.getHistory();
console.log("Snapshots:", history);
// [{ timestamp: 1714000000, sessions: 42, online: 38, away: 4 }, ...]

// Fetch peak analytics
const stats = await presence.getStats();
console.log("Peak:", stats.peak, "at", stats.peakAt);

presence.destroy();

Global / Script Tag usage

The core package attaches itself to window.SimplePresence when loaded in a browser, so you can use it without a bundler:

index.html
<script src="https://unpkg.com/@simple-presence/core"></script>

<script>
  const { SimplePresence } = window.SimplePresence;

  const presence = new SimplePresence({
    tag: "landing",
    appKey: "your-app-key",
    onCountChange: (count) => {
      document.getElementById("visitors").textContent = count;
    },
  });
</script>

Framework integration examples

Building your own integration? Here are examples for popular frameworks:

Vue 3

usePresence.ts
import { ref, onMounted, onUnmounted } from "vue";
import { SimplePresence } from "@simple-presence/core";

export function usePresenceCount(tag: string, appKey: string) {
  const count = ref(0);
  let presence: SimplePresence | null = null;

  onMounted(() => {
    presence = new SimplePresence({
      tag,
      appKey,
      onCountChange: (n) => {
        count.value = n;
      },
    });
    count.value = presence.getCount();
  });

  onUnmounted(() => {
    presence?.destroy();
  });

  return count;
}

Svelte 5

presence.svelte.ts
import { SimplePresence } from "@simple-presence/core";

export function createPresenceCount(tag: string, appKey: string) {
  let count = $state(0);
  let presence: SimplePresence | null = null;

  $effect(() => {
    presence = new SimplePresence({
      tag,
      appKey,
      onCountChange: (n) => {
        count = n;
      },
    });
    count = presence.getCount();

    return () => presence?.destroy();
  });

  return {
    get count() {
      return count;
    },
  };
}

Connection management

Under the hood, the core SDK deduplicates connections. Multiple SimplePresence instances with the same (apiUrl, appKey, tag) tuple share a single WebSocket. The connection is only closed when all instances using it have been destroyed.

How deduplication works

  1. A connection key is computed as `${apiUrl}::${appKey}::${tag}`.
  2. If a connection for that key already exists, a ref-count is incremented and the existing socket is reused.
  3. On destroy(), the ref-count is decremented. The socket closes only when it reaches zero.

Client ID persistence

The SDK generates a stable, anonymous client ID to prevent double-counting across tabs. The ID is persisted using this fallback chain:

  1. localStorage (preferred)
  2. Cookie (fallback if localStorage is unavailable)
  3. In-memory (last resort — resets on page reload)

Next steps

On this page