@joycostudio/argos
v0.1.7
Published
Client-side telemetry for JOYCO sites
Downloads
1,064
Readme
Argos
Client-side telemetry for JOYCO sites. Drop a component into your layout — GPU capabilities, FPS sampling, and loader timing are tracked automatically.
Features
| Feature | Description |
| ------------------------ | -------------------------------------------------------------------------------------------------------- |
| GPU detection | Detects WebGL, WebGPU support, and reads GPU renderer/vendor strings on page land. |
| FPS sampling | Measures real frame timing via requestAnimationFrame. Reports avg, min, max, p5 FPS and frame drop count. Automatic by default (5s), configurable duration, or fully manual with named samples. |
| Loader timing | Manual trackLoaderStart / trackLoaderComplete for measuring loader duration. |
| Zero-config React | <Argos /> component — mount once, telemetry fires automatically. |
| Duplicate protection | Multiple mounts are detected with a dev-mode warning; only the first instance initializes. |
| Background-tab safe | FPS measurement skips frames collected while the tab is hidden. |
| SSR-safe | All browser APIs accessed inside useEffect; no window/document on the server. |
Install
pnpm add @joycostudio/argosQuick start
import { Argos } from '@joycostudio/argos/react'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
{children}
<Argos token={process.env.NEXT_PUBLIC_ARGOS_TOKEN} />
</body>
</html>
)
}This fires two events automatically on page load:
page_land— GPU capabilitiesfps_sample— frame timing stats after a 5-second measurement window
Configuring FPS sampling
// Custom duration (ms)
<Argos token="..." fps={8000} />
// Disable automatic FPS sampling entirely
<Argos token="..." fps={false} />Manual FPS sampling
For sites with loaders or heavy initial animations, disable automatic sampling and control when measurement happens:
import { Argos, startFpsSample, stopFpsSample } from '@joycostudio/argos/react'
// In your layout
<Argos token="..." fps={false} />
// After your loader completes
startFpsSample('post-loader')
// Later, or let it auto-stop after 5s (default)
const data = stopFpsSample('post-loader')You can run multiple named samples concurrently:
startFpsSample('hero-animation')
startFpsSample('scroll-section')
stopFpsSample('hero-animation')
stopFpsSample('scroll-section')Each sample auto-stops after its max duration (default 5s) if not stopped manually. stopFpsSample sends the telemetry event and returns the FpsSampleData (or null).
Loader timing
For sites with a loading screen, measure the time between loader mount and unmount:
import { trackLoaderStart, trackLoaderComplete } from '@joycostudio/argos/react'
import { useEffect } from 'react'
export function Loader() {
useEffect(() => {
trackLoaderStart()
return () => trackLoaderComplete()
}, [])
return <div>Loading…</div>
}The token is resolved from the <Argos /> component or the NEXT_PUBLIC_ARGOS_TOKEN environment variable — no need to pass it again.
Events
All events are sent as POST to the ingestion endpoint with Bearer token auth.
page_land
Fires immediately on mount.
{
"event_type": "telemetry",
"event_name": "page_land",
"origin": "https://example.com",
"path": "/",
"data": {
"webgl": true,
"webgpu": false,
"renderer": "ANGLE (Apple, ANGLE Metal Renderer: Apple M2, ...)",
"vendor": "Google Inc. (Apple)"
}
}fps_sample
Fires when a sample completes (automatically after max duration, or when stopFpsSample is called).
{
"event_type": "telemetry",
"event_name": "fps_sample",
"origin": "https://example.com",
"path": "/",
"data": {
"sample_id": "post-loader",
"fps_avg": 60,
"fps_min": 42,
"fps_max": 60,
"fps_p5": 48,
"frame_drop_count": 3,
"sample_duration_ms": 5002
}
}| Field | Description |
| -------------------- | ------------------------------------------------------------------------ |
| sample_id | Identifier for the sample ("__auto__" for automatic, or custom id). |
| fps_avg | Average FPS across the sample window. |
| fps_min | Lowest instantaneous FPS recorded. |
| fps_max | Highest instantaneous FPS recorded. |
| fps_p5 | 5th percentile FPS — worst-case performance excluding outliers. |
| frame_drop_count | Number of frames longer than 33.33ms (below 30 FPS threshold). |
| sample_duration_ms | Actual measurement duration in milliseconds. |
loader_complete
Fires when trackLoaderComplete() is called.
{
"event_type": "telemetry",
"event_name": "loader_complete",
"origin": "https://example.com",
"path": "/",
"data": {
"loader_ms": 2340
}
}Core API
The core module is framework-agnostic — use it without React if needed.
import { detectGpu, startFpsSample, stopFpsSample, sendEvent, trackLoaderStart, trackLoaderComplete } from '@joycostudio/argos'| Export | Description |
| ------------------------------ | ------------------------------------------------------------------------------- |
| detectGpu() | Returns { webgl, webgpu, renderer, vendor }. SSR-safe (returns defaults). |
| startFpsSample(id?, maxMs?) | Starts collecting frame timings. Default id "default", default max duration 5s. Auto-stops after max duration. |
| stopFpsSample(id?) | Stops the sample, sends the event, and returns FpsSampleData \| null. |
| sendEvent(token, event) | Fire-and-forget POST to the ingestion endpoint with keepalive: true. |
| trackLoaderStart() | Starts the loader timer. |
| trackLoaderComplete() | Stops the timer and sends the loader_complete event. |
| setToken(token) | Sets the auth token (called automatically by <Argos />). |
| getToken() | Reads the stored token or NEXT_PUBLIC_ARGOS_TOKEN env var. |
Package exports
| Import path | Contents |
| ---------------------- | ------------------------------------------------------------------------------------------------- |
| @joycostudio/argos | Core: detectGpu, startFpsSample, stopFpsSample, sendEvent, trackLoaderStart, trackLoaderComplete, setToken, getToken, and all types. |
| @joycostudio/argos/react | React: Argos component, startFpsSample, stopFpsSample, trackLoaderStart, trackLoaderComplete, and types. |
