@trueyy-sdk/web-core

Overview

Framework-agnostic browser primitives - the live client and Helper bridge that power Trueyy's web SDKs.

Most integrations don't need this

If you use React, install @trueyy-sdk/web - it bundles this core and adds the components and hooks. Install @trueyy-sdk/web-core directly only for non-React frontends, vanilla JS, Web Components, or your own framework adapter.

@trueyy-sdk/web-core is the foundation: a stateful WebSocket client, the local Helper bridge, and TypeScript types - with zero UI dependencies. It exists so Trueyy's framework adapters (React today; Vue / Svelte in the future) share one browser-side runtime.

npm

When to install directly

  • You're building vanilla JS or your own framework wrapper (no React).
  • You're building a custom UI and only need Trueyy's data plane (transcript, risk pulses, window analysis).
  • You're embedding Trueyy in a Web Component or non-React micro-frontend.

Install

npm install @trueyy-sdk/web-core

Runs in any modern browser. Requires fetch, WebSocket, crypto.subtle. No Node or build polyfills.

1. TrueyyClient - typed live data client

Holds a session JWT, connects to Trueyy Cloud via WebSocket, auto-refreshes the token before expiry, and exposes a typed event subscription API.

import { TrueyyClient } from "@trueyy-sdk/web-core";

const client = new TrueyyClient({
  token: interviewerToken,                  // JWT minted by @trueyy-sdk/node
  role: "interviewer",                      // "candidate" | "interviewer" | "helper"
  baseUrl: "https://api.trueyy.com",        // optional override
  onTokenExpiring: async () => {
    const res = await fetch("/our-ats/refresh-trueyy-token");
    return res.text();
  },
});

client.connect();

// Typed subscriptions
const unsubRisk = client.on("risk-pulse", (e) => {
  // e: { session_id, kind, severity, evidence }
});
client.on("window-result", (e) => {
  // e: { session_id, window_id, risk, score, summary, modality_breakdown }
});
client.on("live-transcript", (e) => {
  // e: { session_id, speaker, text, is_final, started_at, ended_at }
});
client.on("candidate-status", (e) => {
  // e: { session_id, screen_recording, mic_granted, joined, ... }
});

// Imperative commands (interviewer only)
client.captureNow();
client.startAnalysis();
client.stopAnalysis();
client.startTranscription();
client.stopTranscription();

// Cleanup
unsubRisk();
client.disconnect();

Configuration

interface TrueyyClientOptions {
  token: string;                           // session-scoped JWT
  role: "candidate" | "interviewer" | "helper";
  baseUrl?: string;                        // default: https://api.trueyy.com
  onTokenExpiring?: () => Promise<string>; // called ~30s before exp
}

The client reads the JWT's exp claim locally and refreshes ~30 seconds before expiry: it calls onTokenExpiring, swaps the new JWT in, and the WebSocket reconnects under the new auth.

2. helperBridge - talk to the local Helper

The Trueyy Helper is a small native app the candidate installs; it captures the signals a browser can't. Interviewers don't need it. These helpers let your frontend detect it, hand it a token, and track its status.

import {
  detectHelper,
  helperJoin,
  helperJoinedMeeting,
  helperLeave,
  helperStatus,
  HELPER_DOWNLOAD_URL_MAC,
  HELPER_DOWNLOAD_URL_WIN,
} from "@trueyy-sdk/web-core";

// Is the Helper installed and running?
const ok = await detectHelper();
if (!ok) {
  const url = isMac ? HELPER_DOWNLOAD_URL_MAC : HELPER_DOWNLOAD_URL_WIN;
  window.location.href = url;
}

// Hand the Helper a helper-JWT + Cloud URL. helperToken is minted on your
// backend via trueyy.tokens.mint(round_id, "helper"); sessionId is the round id.
await helperJoin({
  helperToken,
  cortexUrl: "https://api.trueyy.com",
  sessionId: roundId,
});

// When the candidate clicks "Open Meeting":
await helperJoinedMeeting(roundId);

// On unmount / explicit end:
await helperLeave(roundId);

// Poll live state (permissions, mic activity)
const status = await helperStatus();
// → { ok, version, screen_recording, mic_granted, joined }

Event payloads

Every event is fully typed. client.on(event, fn) infers e from SocketEventMap - no casting:

import type {
  RiskPulseEvent, WindowResultEvent, LiveTranscriptEvent,
  CandidateStatusEvent, ImageAnalysisResultEvent,
  SocketEventName, SocketEventMap,
} from "@trueyy-sdk/web-core";

client.on("risk-pulse", (e) => {
  e.severity;   // "info" | "warning" | "critical"
  e.evidence;   // Record<string, unknown>
});

Vanilla JS example

Render risk events into a plain <div> - no framework:

<div id="risk-list"></div>

<script type="module">
  import { TrueyyClient } from "https://esm.sh/@trueyy-sdk/web-core";

  const list = document.getElementById("risk-list");
  const client = new TrueyyClient({
    token: window.__INTERVIEWER_TOKEN__,
    role: "interviewer",
  });
  client.connect();

  client.on("risk-pulse", (e) => {
    const row = document.createElement("div");
    row.textContent = `${e.severity}: ${e.kind}`;
    row.style.borderLeft = `3px solid ${e.severity === "critical" ? "red" : "orange"}`;
    list.prepend(row);
  });
</script>

Building your own framework adapter

Want a Vue, Svelte, Angular, or Solid adapter? The pattern is:

Wrap TrueyyClient in your framework's context primitive (Vue provide/inject, Svelte stores, Solid signals).
Expose hooks / composables / runes that subscribe to specific events.
Re-export the relevant types from @trueyy-sdk/web-core.

The React adapter in packages/web-react is a ~300-line reference. PRs for more adapters are welcome.

On this page

Ask ChatGPT