Overview
Activity tracking lets you record learning sessions, measure time spent, and calculate XP based on performance.
Basic Usage
Start and end an activity:
const activity = timeback . activity . start ({
id: 'lesson-123' ,
name: 'Introduction to Fractions' ,
course: { subject: 'Math' , grade: 3 },
})
// When the user completes the activity
await activity . end ({
totalQuestions: 10 ,
correctQuestions: 8 ,
xpEarned: 80 ,
})
Framework Integration
import { useTimeback } from '@timeback/sdk/react'
import { useEffect } from 'react'
function LessonPage ({ lessonId , lessonName }) {
const timeback = useTimeback ()
useEffect (() => {
if ( ! timeback ) return
const activity = timeback . activity . start ({
id: lessonId ,
name: lessonName ,
course: { subject: 'Math' , grade: 3 },
})
return () => {
activity . end ({
totalQuestions: 10 ,
correctQuestions: 8 ,
xpEarned: 80 ,
})
}
}, [ timeback , lessonId , lessonName ])
return < div > Lesson content... </ div >
}
< script setup >
import { onMounted , onUnmounted } from 'vue'
import { useTimeback } from '@timeback/sdk/vue'
const props = defineProps ([ 'lessonId' , 'lessonName' ])
const timeback = useTimeback ()
let activity
onMounted (() => {
if ( timeback . value ) {
activity = timeback . value . activity . start ({
id: props . lessonId ,
name: props . lessonName ,
course: { subject: 'Math' , grade: 3 },
})
}
})
onUnmounted (() => {
activity ?. end ({
totalQuestions: 10 ,
correctQuestions: 8 ,
xpEarned: 80 ,
})
})
</ script >
< script >
import { onMount , onDestroy } from 'svelte'
import { timeback } from '@timeback/sdk/svelte'
export let lessonId
export let lessonName
let activity
onMount (() => {
if ($ timeback ) {
activity = $ timeback . activity . start ({
id: lessonId ,
name: lessonName ,
course: { subject: 'Math' , grade: 3 },
})
}
})
onDestroy (() => {
activity ?. end ({
totalQuestions: 10 ,
correctQuestions: 8 ,
xpEarned: 80 ,
})
})
</ script >
import { onMount , onCleanup } from 'solid-js'
import { useTimeback } from '@timeback/sdk/solid'
function LessonPage ( props ) {
const timeback = useTimeback ()
let activity
onMount (() => {
if ( ! timeback ) return
activity = timeback . activity . start ({
id: props . lessonId ,
name: props . lessonName ,
course: { subject: 'Math' , grade: 3 },
})
})
onCleanup (() => {
activity ?. end ({
totalQuestions: 10 ,
correctQuestions: 8 ,
xpEarned: 80 ,
})
})
return < div > Lesson content... </ div >
}
Activity Lifecycle
Pause and Resume
Track active vs inactive time:
const activity = timeback . activity . start ({
id: 'lesson-1' ,
name: 'Lesson' ,
course: { subject: 'Math' , grade: 3 },
})
// User pauses (e.g., switches tabs)
activity . pause ()
// User resumes
activity . resume ()
// End with final metrics
await activity . end ({
totalQuestions: 10 ,
correctQuestions: 8 ,
xpEarned: 80 ,
})
End Metrics
When ending an activity, you can provide these metrics:
await activity . end ({
totalQuestions: 10 ,
correctQuestions: 8 ,
xpEarned: 80 ,
masteredUnits: 1 ,
pctComplete: 67 ,
})
Total questions in the activity
Questions answered correctly
XP earned for this activity
Units mastered (optional)
Completion percentage, 0-100 (optional)
totalQuestions and correctQuestions must be provided together.If you provide one, you must provide the other.
Time Override
By default, the SDK automatically tracks active and inactive time. For advanced scenarios where you need to override the timing (e.g., offline sync or batch imports), use the time parameter:
await activity . end ({
totalQuestions: 10 ,
correctQuestions: 8 ,
xpEarned: 80 ,
time: {
active: 1800000 , // 30 minutes active
inactive: 300000 , // 5 minutes paused
},
})
Activity Properties
Access activity state during the session:
const activity = timeback . activity . start ({
id: 'lesson-1' ,
name: 'Lesson' ,
course: { subject: 'Math' , grade: 3 },
})
activity . startedAt
activity . isPaused
activity . elapsedMs
When the activity was started
Whether the activity is paused
Active time in milliseconds (excludes paused time)
Configuration
Activity ingestion requires a Caliper sensor URL in timeback.config.json:
{
"$schema" : "https://timeback.dev/schema.json" ,
"name" : "My App" ,
"sensor" : "https://my-app.example.com/sensors/default" ,
"courses" : [
{
"subject" : "Math" ,
"grade" : 3 ,
"courseCode" : "MATH-3" ,
"sensor" : "https://my-app.example.com/sensors/math" ,
"overrides" : {
"staging" : {
"sensor" : "https://staging.my-app.example.com/sensors/math"
}
}
}
]
}
Sensor Precedence
The SDK resolves sensors in this order (highest to lowest):
Per-course env override: course.overrides[env].sensor
Per-course base override: course.sensor
Top-level default: config.sensor
How It Works
When you call activity.end(), the SDK:
Calculates elapsed time (active vs paused)
Generates Caliper ActivityEvent and TimeSpentEvent
Sends events to the configured sensor
Updates the user’s XP and progress
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Client │────>│ Server │────>│ Caliper API │
│ activity. │ │ /activity │ │ │
│ end() │ │ handler │ │ │
└──────────────┘ └──────────────┘ └──────────────┘
Best Practices
Start activities in useEffect/onMount
Always start activities in lifecycle hooks to ensure proper cleanup.
End activities on cleanup
Use cleanup functions to ensure activities are ended when components unmount.
Handle missing timeback client
Check if the timeback client exists before creating activities (it may be null if not
authenticated).
Use meaningful activity IDs
Use stable, unique IDs that identify the specific lesson or content piece.
Next Steps