BIO.RE
Analytics

Session Heartbeat

Public keep-alive ping. Updates lastActivityAt and increments duration by a fixed 30 seconds. Designed to fire every 30s while the page is in the foreground.

PATCH /api/v1/analytics/session/:id/heartbeat โ€” ๐ŸŒ Public ยท Rate limit: 5 req / 30 s

Marks the session as still active and adds 30 seconds to its rolling duration counter. Call once every ~30 seconds while the user has the page in the foreground; stop when they background the tab or close the page. The endpoint hard-codes a +30 increment regardless of how long it's actually been since the last beat, so don't call it more often than once per 30s โ€” you'll over-count time on session.

duration is incremented by a hard-coded 30s per call. The server does NOT measure the elapsed wall-clock time between heartbeats. Two beats in 1 second still add 60s to duration. The throttle (@Throttle(5, 30) โ€” 5 req per 30s) is what protects against accidental spam, but you should still pace your client to one beat per ~30s.

Failures are silent. Service does update.catch(() => {}) โ€” if the session id doesn't exist or DB is unhappy, you still get 200 { ok: true }. No way for the client to detect that the session is gone via this endpoint; only the session-create response's null id signals real rejection.

Pair with the Page Visibility API. The right cadence is "every 30 seconds while document.visibilityState === 'visible'." Sending heartbeats from a backgrounded tab inflates active-time metrics. See the code sample.

Request

Path parameters

ParamTypeRequiredNotes
idstringโœ“Session id from POST /analytics/session. Not validated by ParseUUIDPipe โ€” any string is accepted; mismatches no-op.

Headers

HeaderRequiredNotes
Content-Type: application/jsonoptionalBody is empty.

No body, no auth.

Response

200 OK โ€” ApiResponseOf<HeartbeatResultDto>

{ "success": true, "data": { "ok": true } }

ok: true means "request reached the service" โ€” not "DB write succeeded" (see warn callout).

Errors

HTTPReason
429Over 5 req / 30s from this IP. (Throttle is per-IP, not per-session โ€” multiple tabs share the budget.)

Side effects

  1. ThrottleGuard (5 req / 30s).
  2. analyticsDb.analyticsSession.update({ where: { id }, data: { lastActivityAt: new Date(), duration: { increment: 30 } } }).catch(() => {}). Single SQL UPDATE, no read-then-write.
  3. Return { ok: true } regardless of whether any row was matched.

Code samples

curl -X PATCH https://api.bio.re/api/v1/analytics/session/ses-uuid/heartbeat
async function sessionHeartbeat(sessionId: string): Promise<void> {
  await fetch(`https://api.bio.re/api/v1/analytics/session/${sessionId}/heartbeat`, {
    method: 'PATCH',
  }).catch(() => {}); // never throw from instrumentation
}
// Fire a heartbeat every 30s, but only while the tab is visible.
// Pauses on visibilitychange = 'hidden', resumes on 'visible'.
function startHeartbeat(sessionId: string): () => void {
  let timer: number | null = null;

  const start = () => {
    if (timer != null) return;
    timer = window.setInterval(() => sessionHeartbeat(sessionId), 30_000);
  };
  const stop = () => {
    if (timer != null) { clearInterval(timer); timer = null; }
  };

  const onVisibility = () => {
    if (document.visibilityState === 'visible') start(); else stop();
  };

  document.addEventListener('visibilitychange', onVisibility);
  if (document.visibilityState === 'visible') start();

  // Return cleanup
  return () => {
    document.removeEventListener('visibilitychange', onVisibility);
    stop();
  };
}

Try it

PATCH
/api/v1/analytics/session/{id}/heartbeat

Path Parameters

id*string

Response Body

application/json

curl -X PATCH "https://loading/api/v1/analytics/session/string/heartbeat"
{
  "success": true,
  "data": {
    "ok": true
  }
}

Source

SourcePathLines
Controllerapps/api-core/src/modules/analytics/analytics.controller.ts157โ€“166 (sessionHeartbeat)
DTO (response)apps/api-core/src/modules/analytics/dto/analytics-client-response.dto.ts36โ€“39 (HeartbeatResultDto)
Serviceapps/api-core/src/modules/analytics/traffic-tracking.service.ts195โ€“203 (heartbeat โ€” fixed +30 increment, silent failure)
Prisma modelpackages/prisma-analytics/prisma/schema.prismaAnalyticsSession.duration / lastActivityAt lines 53โ€“54

On this page