Skip to main content

Data Formats

The scorm-again library supports multiple data commit formats for sending SCORM data to your LMS. You can also customize how data is sent and how responses are handled using handler functions.

Data Commit Formats

The dataCommitFormat setting controls how SCORM data is formatted when sent to the LMS. Three formats are supported:

  • json (default) - Hierarchical JSON object
  • flattened - Flat key-value pairs with dot notation
  • params - URL query string parameters

JSON Format

The JSON format sends data as a hierarchical object matching the SCORM CMI structure.

Configuration:

const api = new Scorm2004API({
lmsCommitUrl: 'https://your-lms.com/api/commit',
dataCommitFormat: 'json'
});

Example SCORM 1.2 JSON Output:

{
"cmi": {
"core": {
"student_id": "12345",
"student_name": "John Doe",
"lesson_location": "page5",
"lesson_status": "completed",
"score": {
"raw": "85",
"min": "0",
"max": "100"
},
"total_time": "00:15:30",
"session_time": "00:05:30",
"exit": "suspend"
},
"suspend_data": "bookmark=chapter3;progress=75",
"launch_data": "",
"comments": "",
"comments_from_lms": ""
}
}

Example SCORM 2004 JSON Output:

{
"cmi": {
"learner_id": "12345",
"learner_name": "John Doe",
"location": "page5",
"completion_status": "completed",
"success_status": "passed",
"score": {
"scaled": "0.85",
"raw": "85",
"min": "0",
"max": "100"
},
"total_time": "PT15M30S",
"session_time": "PT5M30S",
"exit": "suspend",
"suspend_data": "bookmark=chapter3;progress=75"
}
}

Use Cases:

  • Modern REST APIs
  • Easy to parse on the server
  • Natural representation of SCORM data
  • Works well with JSON-based databases

Flattened Format

The flattened format sends data as a flat object with dot-notation keys.

Configuration:

const api = new Scorm12API({
lmsCommitUrl: 'https://your-lms.com/api/commit',
dataCommitFormat: 'flattened'
});

Example SCORM 1.2 Flattened Output:

{
"cmi.core.student_id": "12345",
"cmi.core.student_name": "John Doe",
"cmi.core.lesson_location": "page5",
"cmi.core.lesson_status": "completed",
"cmi.core.score.raw": "85",
"cmi.core.score.min": "0",
"cmi.core.score.max": "100",
"cmi.core.total_time": "00:15:30",
"cmi.core.session_time": "00:05:30",
"cmi.core.exit": "suspend",
"cmi.suspend_data": "bookmark=chapter3;progress=75"
}

Example SCORM 2004 Flattened Output:

{
"cmi.learner_id": "12345",
"cmi.learner_name": "John Doe",
"cmi.location": "page5",
"cmi.completion_status": "completed",
"cmi.success_status": "passed",
"cmi.score.scaled": "0.85",
"cmi.score.raw": "85",
"cmi.score.min": "0",
"cmi.score.max": "100",
"cmi.total_time": "PT15M30S",
"cmi.session_time": "PT5M30S",
"cmi.exit": "suspend",
"cmi.suspend_data": "bookmark=chapter3;progress=75"
}

Use Cases:

  • Legacy LMS systems expecting flat data
  • Simple storage in key-value databases
  • Easy to map to database columns
  • Simpler to process in some languages

Params Format

The params format sends data as URL query string parameters.

Configuration:

const api = new Scorm2004API({
lmsCommitUrl: 'https://your-lms.com/api/commit',
dataCommitFormat: 'params'
});

Example SCORM 1.2 Params Output:

?cmi.core.student_id=12345&cmi.core.student_name=John%20Doe&cmi.core.lesson_location=page5&cmi.core.lesson_status=completed&cmi.core.score.raw=85&cmi.core.score.min=0&cmi.core.score.max=100&cmi.core.total_time=00:15:30&cmi.core.session_time=00:05:30&cmi.core.exit=suspend&cmi.suspend_data=bookmark%3Dchapter3%3Bprogress%3D75

Example SCORM 2004 Params Output:

?cmi.learner_id=12345&cmi.learner_name=John%20Doe&cmi.location=page5&cmi.completion_status=completed&cmi.success_status=passed&cmi.score.scaled=0.85&cmi.score.raw=85&cmi.score.min=0&cmi.score.max=100&cmi.total_time=PT15M30S&cmi.session_time=PT5M30S&cmi.exit=suspend&cmi.suspend_data=bookmark%3Dchapter3%3Bprogress%3D75

Use Cases:

  • GET request endpoints
  • Simple PHP $_GET processing
  • Lightweight data transmission
  • Legacy systems expecting query parameters

Handler Functions

Handler functions allow you to customize how data is transformed before sending to the LMS and how responses are processed.

requestHandler

The requestHandler function transforms the commit object before sending it to the LMS.

Function Signature:

requestHandler: (commitObject: CommitObject) => CommitObject

Default Behavior:

By default, requestHandler is the identity function (no transformation).

Example - Adding Custom Fields:

const api = new Scorm2004API({
lmsCommitUrl: 'https://your-lms.com/api/commit',
requestHandler: function(commitObject) {
// Add custom metadata
commitObject.timestamp = new Date().toISOString();
commitObject.courseId = 'COURSE-12345';
commitObject.userId = 'USER-67890';

return commitObject;
}
});

Example - Modifying SCORM Data:

const api = new Scorm12API({
lmsCommitUrl: 'https://your-lms.com/api/commit',
requestHandler: function(commitObject) {
// Force completion status
if (commitObject.cmi.core.score.raw >= 80) {
commitObject.cmi.core.lesson_status = 'passed';
}

// Compress suspend data
if (commitObject.cmi.suspend_data) {
commitObject.cmi.suspend_data = LZString.compress(
commitObject.cmi.suspend_data
);
}

return commitObject;
}
});

Example - Wrapping Data:

const api = new Scorm2004API({
lmsCommitUrl: 'https://your-lms.com/api/commit',
requestHandler: function(commitObject) {
// Wrap in API envelope
return {
type: 'scorm_commit',
version: '2004',
data: commitObject,
metadata: {
timestamp: Date.now(),
clientVersion: '3.0.0'
}
};
}
});

responseHandler

The responseHandler function processes async fetch Response objects when useAsynchronousCommits=true.

Function Signature:

responseHandler: async (response: Response) => Promise<ResultObject>

Expected Return Format:

interface ResultObject {
result: boolean; // Required: true for success, false for failure
errorCode?: number; // Optional: SCORM error code
}

Default Behavior:

The default handler expects JSON in the format: { "result": true, "errorCode": 0 }

Example - Custom Response Format:

const api = new Scorm2004API({
lmsCommitUrl: 'https://your-lms.com/api/commit',
useAsynchronousCommits: true,
responseHandler: async function(response) {
const responseObj = await response.json();

// LMS returns: { "success": true, "error": 0 }
return {
result: responseObj.success,
errorCode: responseObj.error
};
}
});

Example - Handling Different Status Codes:

const api = new Scorm12API({
lmsCommitUrl: 'https://your-lms.com/api/commit',
useAsynchronousCommits: true,
responseHandler: async function(response) {
if (!response.ok) {
console.error('LMS commit failed:', response.status);
return {
result: false,
errorCode: 101 // General exception
};
}

const data = await response.json();
return {
result: data.success === true,
errorCode: data.errorCode || 0
};
}
});

Example - XML Response:

const api = new Scorm2004API({
lmsCommitUrl: 'https://your-lms.com/api/commit',
useAsynchronousCommits: true,
responseHandler: async function(response) {
const xmlText = await response.text();
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlText, 'text/xml');

const success = xmlDoc.querySelector('success')?.textContent === 'true';
const errorCode = parseInt(
xmlDoc.querySelector('errorCode')?.textContent || '0'
);

return {
result: success,
errorCode: errorCode
};
}
});

xhrResponseHandler

The xhrResponseHandler function processes synchronous XMLHttpRequest responses when useAsynchronousCommits=false (default).

Function Signature:

xhrResponseHandler: (xhr: XMLHttpRequest) => ResultObject

Expected Return Format:

interface ResultObject {
result: boolean; // Required: true for success, false for failure
errorCode?: number; // Optional: SCORM error code
}

Default Behavior:

The default handler expects JSON in the format: { "result": true, "errorCode": 0 }

Example - Custom Response Format:

const api = new Scorm12API({
lmsCommitUrl: 'https://your-lms.com/api/commit',
xhrResponseHandler: function(xhr) {
if (xhr.status !== 200) {
return {
result: false,
errorCode: 101
};
}

const responseObj = JSON.parse(xhr.responseText);

// LMS returns: { "success": true, "code": 0 }
return {
result: responseObj.success,
errorCode: responseObj.code
};
}
});

Example - Plain Text Response:

const api = new Scorm2004API({
lmsCommitUrl: 'https://your-lms.com/api/commit',
xhrResponseHandler: function(xhr) {
// LMS returns: "OK" or "ERROR"
const success = xhr.responseText.trim() === 'OK';

return {
result: success,
errorCode: success ? 0 : 101
};
}
});

Example - XML Response:

const api = new Scorm12API({
lmsCommitUrl: 'https://your-lms.com/api/commit',
xhrResponseHandler: function(xhr) {
const xmlDoc = xhr.responseXML;

const success = xmlDoc.querySelector('success')?.textContent === 'true';
const errorCode = parseInt(
xmlDoc.querySelector('errorCode')?.textContent || '0'
);

return {
result: success,
errorCode: errorCode
};
}
});

Combining Formats and Handlers

You can combine data formats with handler functions for complete customization:

const api = new Scorm2004API({
lmsCommitUrl: 'https://your-lms.com/api/commit',
dataCommitFormat: 'flattened',

requestHandler: function(commitObject) {
// Add authentication token
commitObject['auth_token'] = 'YOUR_TOKEN';

// Add course metadata
commitObject['course_id'] = 'COURSE-123';
commitObject['sco_id'] = 'SCO-456';

return commitObject;
},

xhrResponseHandler: function(xhr) {
// Custom response parsing
const data = JSON.parse(xhr.responseText);

if (data.status === 'success') {
return { result: true, errorCode: 0 };
} else {
console.error('Commit failed:', data.message);
return { result: false, errorCode: data.errorCode || 101 };
}
}
});

Common Use Cases

Adding Authentication Headers

const api = new Scorm2004API({
lmsCommitUrl: 'https://your-lms.com/api/commit',
xhrHeaders: {
'Authorization': 'Bearer YOUR_JWT_TOKEN',
'X-Course-ID': 'COURSE-123'
}
});

Compressing Suspend Data

// Using LZ-String library
import LZString from 'lz-string';

const api = new Scorm12API({
lmsCommitUrl: 'https://your-lms.com/api/commit',
requestHandler: function(commitObject) {
if (commitObject.cmi.suspend_data) {
commitObject.cmi.suspend_data = LZString.compressToBase64(
commitObject.cmi.suspend_data
);
}
return commitObject;
}
});

Encrypting Sensitive Data

// Using CryptoJS library
import CryptoJS from 'crypto-js';

const api = new Scorm2004API({
lmsCommitUrl: 'https://your-lms.com/api/commit',
requestHandler: function(commitObject) {
const secretKey = 'YOUR_SECRET_KEY';

// Encrypt suspend data
if (commitObject.cmi.suspend_data) {
commitObject.cmi.suspend_data = CryptoJS.AES.encrypt(
commitObject.cmi.suspend_data,
secretKey
).toString();
}

return commitObject;
}
});

Logging All Commits

const api = new Scorm12API({
lmsCommitUrl: 'https://your-lms.com/api/commit',
requestHandler: function(commitObject) {
// Log to analytics
console.log('Committing data:', commitObject);

// Send to logging service
fetch('https://analytics.example.com/log', {
method: 'POST',
body: JSON.stringify({
event: 'scorm_commit',
data: commitObject,
timestamp: new Date().toISOString()
})
});

return commitObject;
}
});

Handling Offline/Online

const api = new Scorm2004API({
lmsCommitUrl: 'https://your-lms.com/api/commit',
xhrResponseHandler: function(xhr) {
if (xhr.status === 0) {
// Network error - likely offline
console.warn('Offline - commit will retry when online');

// Store for later
const commitData = xhr.responseText;
localStorage.setItem('pending_commit', commitData);

// Return success to prevent error display
return { result: true, errorCode: 0 };
}

// Normal processing
const data = JSON.parse(xhr.responseText);
return {
result: data.result,
errorCode: data.errorCode || 0
};
}
});

Best Practices

1. Validate Response Format

Always validate the response format before processing:

responseHandler: async function(response) {
try {
const data = await response.json();

if (typeof data.result !== 'boolean') {
console.error('Invalid response format');
return { result: false, errorCode: 101 };
}

return {
result: data.result,
errorCode: data.errorCode || 0
};
} catch (error) {
console.error('Failed to parse response:', error);
return { result: false, errorCode: 101 };
}
}

2. Don't Modify Original Objects

Create copies when transforming data:

requestHandler: function(commitObject) {
// Create a shallow copy
const modified = { ...commitObject };

// Add custom fields
modified.timestamp = Date.now();

return modified;
}

3. Handle Errors Gracefully

xhrResponseHandler: function(xhr) {
try {
if (xhr.status >= 200 && xhr.status < 300) {
const data = JSON.parse(xhr.responseText);
return {
result: data.success === true,
errorCode: data.errorCode || 0
};
} else {
console.error(`LMS returned status ${xhr.status}`);
return { result: false, errorCode: 101 };
}
} catch (error) {
console.error('Error processing response:', error);
return { result: false, errorCode: 101 };
}
}

4. Keep Handlers Simple

Avoid heavy processing in handlers to prevent blocking:

// Good - lightweight
requestHandler: function(commitObject) {
commitObject.timestamp = Date.now();
return commitObject;
}

// Bad - heavy processing
requestHandler: function(commitObject) {
// Don't do this - too much work
for (let i = 0; i < 1000000; i++) {
// Complex calculation
}
return commitObject;
}