Skip to main content

Client

activity.current

The currently active activity instance, or null if none is running.

activity.start(params)

Creates and starts a new activity tracker. Heartbeats begin immediately unless time: false is passed.
Only one activity can be active at a time. Calling start() while an activity is already running throws an error. End the current activity first.
id
string
required
Activity slug: a stable, URL-safe identifier for the learning object (e.g. "fractions-with-like-denominators"). Used to construct the canonical activity URL in Caliper events.
name
string
required
Human-readable display name (e.g. "Fractions with Like Denominators"). Sent as object.activity.name in Caliper events.
course
object
required
Course selector: must match a course in timeback.config.json. Either { subject, grade } or { code }.
runId
string
Unique identifier for correlating events across sessions. If omitted, the SDK generates a new UUID. Pass the same runId when resuming a stateful activity to link heartbeats with the eventual completion event.
time
object | false
Time tracking configuration. All fields are optional: defaults work well for most apps.
Set to false to disable client-side time tracking entirely. When disabled, no heartbeats are sent, no visibility handlers are registered, and end() skips the final time flush. Use this when e.g. time is managed server-side.
onError
(error, context) => void
Called when a time-spent flush or completion submission fails. When retryAttempts is configured, only fires after all retries are exhausted. Time-spent errors are non-fatal: the SDK continues tracking. Completion errors (from end()) are also surfaced here before being re-thrown.
onPause
() => void
Called when the activity is paused, either explicitly via pause() or automatically when the hidden timeout fires.
onResume
() => void
Called when the activity resumes, either via resume() or when the user returns after a hidden timeout.
onFlush
(elapsedMs: number) => void
Called after each successful heartbeat flush with the active milliseconds reported in that window.

Examples

const activity = timeback.activity.start({
	id: 'lesson-1',
	name: 'Intro to Fractions',
	course: { subject: 'Math', grade: 3 },
})

Activity instance

The object returned by activity.start().

Properties

id
string
The activity slug passed to start().
startedAt
Date
When the activity was started.
isPaused
boolean
Whether the activity is currently paused.
isEnded
boolean
Whether end() has completed successfully.Once true, the activity is no longer active and a new one can be started.
runId
string
Unique identifier correlating heartbeats and completion events for this run.
totalActiveMs
number
Cumulative active time across all flushed heartbeat windows plus the current in-progress window.
elapsedMs
number
Active time for the current heartbeat window only. Resets to 0 after each flush.

Methods

pause()
Flushes accumulated time, then stops heartbeats until resume() is called. Fires onPause if provided.
resume()
Starts a fresh tracking window and restarts heartbeats. Fires onResume if provided.
flushTimeSpent()
Promise<void>
Manually flush accumulated time to the server. No-op when time tracking is disabled or the activity is paused. Serialized — only one flush can be in flight at a time.
activity.pause() // Flushes time, stops heartbeats
activity.resume() // Fresh window, restarts heartbeats
onPause and onResume callbacks also fire for automatic state changes like hidden timeouts — use them to keep your UI in sync without polling isPaused.

activity.end(data?)

Ends the activity. Always flushes remaining time data. If completion data is provided, also sends an ActivityCompletedEvent.
// Time-only flush (no completion recorded)
await activity.end()

// With completion
await activity.end({
	xpEarned: 80,
	totalQuestions: 10,
	correctQuestions: 8,
	pctComplete: 100,
})
If the network call fails, the activity remains usable so the caller can retry. onError fires with { type: 'completion' } before the error is re-thrown.
const activity = timeback.activity.start({
	id: 'lesson-1',
	name: 'Fractions',
	course: { subject: 'Math', grade: 3 },
	onError: (err, ctx) => {
		if (ctx.type === 'completion') showRetryButton()
	},
})

Completion data

xpEarned
number
required
XP earned for this activity. Must follow the 1 XP = 1 focused minute rule.
totalQuestions
number
Total questions in the activity. Must be paired with correctQuestions.
correctQuestions
number
Questions answered correctly. Must be paired with totalQuestions.
masteredUnits
number
Number of new units (lessons) the student mastered during this activity. This is an incremental count, not a cumulative total. The server sums these across submissions and divides by totalLessons to auto-compute pctComplete when it is omitted. See Course progress for details.
pctComplete
number
Course completion percentage (0—100). If omitted and masteredUnits is provided, the server auto-computes this from gradebook history. If provided, the server forwards it as-is.
time
object
Override the SDK’s automatic time tracking with explicit values. Use this for advanced scenarios like offline sync or batch imports where the SDK wasn’t tracking time in real-time.
totalQuestions and correctQuestions must be provided together.If you provide one, you must provide the other.

Server

timeback.activity.record(params)

Records an activity completion from the backend. Sends an ActivityCompletedEvent to the Caliper API and runs the full pipeline including gradebook and XP. See the server adapter docs for setup.
user
object
required
Student identity. Provide email (required) and optionally timebackId. See Identity for how users are resolved.
activity
object
required
Activity identity.
metrics
object
required
Completion metrics.
time
object
Optional time data. Include when the backend tracks accumulated session time — for example, when the frontend uses time: false, or for offline sync and batch imports.
runId
string
UUID correlating this completion with frontend heartbeats. Should match the runId persisted when the activity was started on the client.
totalQuestions and correctQuestions must be provided together.If you provide one, you must provide the other.

Examples

await timeback.activity.record({
	user: { email: 'student@example.com' },
	activity: {
		id: 'lesson-1',
		name: 'Fractions',
		course: { code: 'MATH-3' },
	},
	metrics: {
		totalQuestions: 20,
		correctQuestions: 16,
		xpEarned: 150,
		masteredUnits: 2,
		pctComplete: 100,
	},
	runId: savedProgress.runId,
})

With time data

Only include time when the frontend uses time: false, meaning your server owns time tracking. Do not pass time here if the frontend is sending heartbeats (the default), or time will be double-counted.
await timeback.activity.record({
	user: { email: 'student@example.com' },
	activity: {
		id: 'lesson-1',
		name: 'Fractions',
		course: { code: 'MATH-3' },
	},
	metrics: {
		totalQuestions: 20,
		correctQuestions: 16,
		xpEarned: 150,
	},
	time: {
		activeMs: 1200000, // 20 minutes total active time
		inactiveMs: 120000, // 2 minutes total paused time
	},
	runId: savedProgress.runId,
})