xAPI¶
Statement delivery to an LRS, alongside SCORM rather than instead of it. A course can run both: report to the LMS through ILmsRuntime and send statements through IXApiRuntime, and neither knows about the other.
| Folder | What lives here |
|---|---|
Runtime/ | IXApiRuntime, the live implementation, and the stub |
Data/ | Statement, actor, verb, activity, result, context |
Helpers/ | XApiVerbs and XApiStatements — the shapes you actually send |
Transport/ | The queue, the retry policy, and the UnityWebRequest sender |
Serialization/ | Hand-written JSON, because JsonUtility cannot express a statement |
XApiRuntimeFactory sits at the root and builds a runtime from an XApiEndpointConfig.
The one thing to know about the contract¶
Everything public returns Task<bool>: SendStatementAsync, InitializeAsync, FinishAsync, and every helper in XApiStatements.
Internally delivery is a tri-state — delivered, retryable, or rejected — and that distinction matters a great deal, because a retryable failure must stay queued and a rejected statement must not. But it is deliberately internal. true means "accepted or safely queued"; false means "the LRS refused this and retrying will not help". A course cannot act usefully on the difference between a timeout and a 503 — the queue already handles both — and exposing a third state would make every call site a switch over cases most courses would get wrong.
So the queue sees three outcomes and callers see two. If you need the detail, it is in the console: every refusal is logged with its status code.
Statements a course does not have to build¶
XApiStatements has the dozen shapes that cover most courses — PassedAsync, AnsweredAsync, SuspendedAsync and so on. They exist because the specification has rules that are easy to break by hand, and an LRS enforces them by rejecting the whole statement:
- A raw score outside its stated
min..maxrange is a rejection, not a clamped score.XApiScore.Rawclamps and warns instead, because a slightly wrong number on a report beats a lost pass. - A registration must be a UUID.
- A verb needs an id and an activity needs an id; empty strings are checked before send rather than after refusal.