🌐 English · Español · Français · Português · Deutsch · Polski
An interactive, fully working example application that demonstrates every feature of the
@studiolxd/xapi library — the
framework-agnostic core, the ./react adapter, and the launch/vanilla/CDN paths.
Built with React 19 + TypeScript + Vite (using the @studiolxd/xapi/react adapter). Runs
entirely in the browser by default against createMemoryLrs() — an in-memory xAPI
implementation — so no real LRS is required to try it. A Connection tab also lets you
point the demo at any real LRS (your own studiolxd/lrs,
Veracity, SCORM Cloud, Learning Locker…) with Basic auth.
From the repository root (the demo is an npm workspace):
npm install
npm run dev:exampleOpen http://localhost:5173 in your browser.
The header has an xAPI version switcher (1.0.3 / 2.0) and a mock / real LRS badge.
Switching either remounts XapiProvider with a fresh client — the library has no
"reconfigure" method, so a fresh client is the correct way to point at a different LRS or
version.
| Tab | Features demonstrated |
|---|---|
| Connection | Live XapiStatus, switching between the mock LRS and a real one, client.about() |
| Statement Builder | client.buildStatement(), VERBS, client.sendStatement() — build a statement from actor/verb/object/result and send it |
| Query | client.getStatements(query) with filters, client.getMoreStatements() pagination |
| Voiding | client.voidStatement(), client.getVoidedStatement() — statements are immutable, voiding is how you retract one |
| State | getState / setState / deleteState / getStateIds — per-actor, per-activity storage |
| Activity Profile | getActivityProfile / setActivityProfile / deleteActivityProfile / getActivityProfileIds — shared per-activity storage |
| Agent Profile | getAgentProfile / setAgentProfile / deleteAgentProfile / getAgentProfileIds — shared per-agent storage |
| Launch | parseXapiLaunch() — parsing a TinCan/Rustici-style launch URL |
| Vanilla / CDN | createXapiClient() outside React, plus the window.Xapi IIFE/CDN snippet |
| About & Versions | 1.0.3 vs 2.0 cheat sheet, live client.about() |
The app uses createMemoryLrs() from @studiolxd/xapi — an in-memory implementation of the
xAPI protocol (the same one the library's own test suite runs against). It's created once at
module scope, so your demo data survives version toggles.
// App.tsx
const memoryLrs = createMemoryLrs();
<XapiProvider
key={`${version}-${connection.mode}-${connection.endpoint}`}
options={{ endpoint: 'https://mock.lrs/xapi', version, fetch: memoryLrs.fetch }}
>
{/* all components that call useXapiClient() go here */}
</XapiProvider>Use the Connection tab to switch to a real LRS: enter its endpoint and Basic auth credentials and click Connect.
@studiolxd/xapi is a framework-agnostic xAPI (Experience API / Tin Can) client: a core with
adapters for React, Vue, Angular and Svelte, plus a ./server subpath with protocol helpers
for implementing an LRS. This demo uses the React adapter.
1. Provider + Hooks
import { XapiProvider, useXapiClient, useXapiStatus } from '@studiolxd/xapi/react';
function Root() {
return (
<XapiProvider options={{ endpoint: 'https://lrs.example.com/xapi', auth: { username, password } }}>
<Lesson />
</XapiProvider>
);
}
function Lesson() {
const client = useXapiClient(); // stable XapiClient
const status = useXapiStatus(); // reactive XapiStatus (re-renders on every request)
}2. Result-Based Error Handling
Every network method returns Promise<Result<T, XapiError>> — no thrown exceptions:
const result = await client.sendStatement(statement);
if (result.ok) {
console.log('stored with id', result.value);
} else {
console.error(`${result.error.kind}: ${result.error.message}`);
}3. Statements are built, not hand-typed
const statement = client.buildStatement({
actor: { mbox: 'mailto:learner@example.com' },
verb: VERBS.completed, // or a plain IRI string
object: 'https://example.com/course/1', // shorthand for an Activity
result: { success: true, score: { raw: 90, min: 0, max: 100 } },
});id and timestamp are generated automatically if omitted.
4. Dual version support
The same client speaks both xAPI 1.0.3 and 2.0 — pick it with createXapiClient({ version }).
See the About & Versions tab and packages/xapi/docs/version-differences.md
for exactly what's abstracted.
// Statements
client.sendStatement(statement)
client.sendStatements(statements)
client.getStatement(id)
client.getVoidedStatement(id)
client.getStatements(query)
client.getMoreStatements(more)
client.voidStatement(targetId, actor?)
// Documents (State / Activity Profile / Agent Profile)
client.getState(activityId, stateId, opts?)
client.setState(activityId, stateId, value, opts?)
client.deleteState(activityId, stateId, opts?)
client.getStateIds(activityId, opts?)
// … same 4-method shape for getActivityProfile* and getAgentProfile*
// Other resources
client.getActivity(activityId)
client.getPerson(agent)
client.about()
// Launch
parseXapiLaunch(url?)
createXapiClientFromLaunch(url?, extra?)src/
├── main.tsx # Vite entry point
├── App.tsx # Version/connection switcher + XapiProvider + tab navigation
├── connection.ts # ConnectionConfig type + defaults
├── XapiConnectionContext.tsx # Shares mock/real connection state across sections
├── ResultView.tsx # Renders a Result<T, XapiError> as ok/error + JSON
├── App.css / index.css # Design system (reused from @studiolxd/scorm's demo)
└── sections/
├── ConnectionSection.tsx # Live status, mock/real switch, about()
├── StatementBuilderSection.tsx
├── QuerySection.tsx
├── VoidingSection.tsx
├── StateSection.tsx
├── ActivityProfileSection.tsx
├── AgentProfileSection.tsx
├── LaunchSection.tsx
├── PlatformsSection.tsx # createXapiClient() + window.Xapi CDN snippet
└── AboutSection.tsx
- Build: Vite 8 +
@vitejs/plugin-react. Production build istsc -b && vite build— type-checking then bundling. - Language: TypeScript 5.9, strict mode (
tsconfig.app.json). - Linting: ESLint 9 flat config (
typescript-eslint,eslint-plugin-react-hooks,eslint-plugin-react-refresh). Run withnpm run lint.
| Script | Command | Description |
|---|---|---|
npm run dev |
vite |
Start dev server at http://localhost:5173 |
npm run build |
tsc -b && vite build |
Type-check + production bundle |
npm run lint |
eslint . |
Lint all .ts / .tsx files |
npm run preview |
vite preview |
Preview the production build locally |
From the repository root: npm run dev:example runs npm run dev --workspace=example.
MIT — see LICENSE.