ANALYTICS / CDN
Analytics via CDN
Complete guide to receiving, identifying, indexing and reading P2LRN learner results via CDN, without a database.
1. What Analytics does, and what src means
Player opens a .p2lrn course and records learning activity. A supported offline delivery or Player's result recorder generates one .p2lrn-result file per attempt. Analytics does not read a course package to discover learner results; it reads result files that already exist.
<script src="https://p2lrn.org/analytics/current/dist/p2lrn-analytics.js"></script>
<p2lrn-analytics id="panel.analytics"
src="/results/result-001.p2lrn-result"></p2lrn-analytics>The src attribute is the URL of a result file the browser can access, not a course URL. id="panel.analytics" is just the panel's HTML identifier, not a course or learner ID. Analytics checks the result's SHA-256 and computes available metrics without requiring a database or analytics endpoint.
Release status: this page describes the result-file src behavior developed on the P2LRN redesign branch. These APIs require publishing the matching Player/Analytics build to NPM and CDN. Older public bundles may not support them yet; do not assume a production CDN URL already contains the changes.
2. The complete workflow: learner to teacher
- Prepare: the teacher creates the course and chooses either platform authentication or individual learner codes.
- Complete: the learner opens the course in Player or a supported offline delivery and completes an attempt.
- Generate: a
.p2lrn-resultfile is created, including course, declared learner identity, attempt and captured activity. - Deliver: the learner returns the file by the agreed channel, or an authenticated platform stores it in its authorized file storage. Player does not automatically transmit results to Analytics or another computer.
- Inspect: the teacher imports local files into Analytics or uses an authorized
srcURL pointing to those results. - Update: as new files arrive, re-import them or add them to the results folder and refresh its static index.
The course .p2lrn and each attempt's .p2lrn-result are two different files serving different purposes.
3. Identify the learner: two options
A. Authenticated platform user
An LMS or website first authenticates the learner and supplies Player with an issuer (identity provider) and stable subjectId (user identifier). These are recorded in the result, allowing Analytics to group attempts for that identity and issuer.
{
mode: 'platform',
issuer: 'campus.ejemplo',
subjectId: 'usuario-123',
label: 'Ana'
}These values must come from a trusted authenticated host session, not a form field learners can edit. Analytics can read an identity claim, but it does not independently prove that the host actually authenticated the learner.
B. No platform or accounts: individual teacher code
The teacher assigns a different code to each learner (for example ALU-007) and keeps their own mapping from code to student. In offline deliveries, learners enter that code when they start. On an account-free website, Player must receive it before result capture starts. Learners must keep the same code and issuer for all their attempts.
{
mode: 'code',
issuer: 'my-school',
code: 'ALU-007',
label: 'Ana'
}Important: if two people enter the same code, Analytics may group their results together. A code links result files; it does not authenticate who completed the activity.
4. Generate a result in Player via CDN
A web page using Player can enable result capture and download the file when an attempt ends. This example uses a teacher code; replace ALU-007 with the correct individual code and enable capture before the learner starts.
<script src="https://p2lrn.org/player/current/dist/p2lrn.js"></script>
<p2lrn-player id="course-player" src="/courses/course.p2lrn"></p2lrn-player>
<button id="download-result" type="button">Download my result</button>
<script>
window.P2LRNReady.then(() => {
const player = document.getElementById('course-player');
// The teacher assigned ALU-007 to this learner before starting.
player.startResultCapture({
identity: {mode:'code', issuer:'my-school', code:'ALU-007'}
});
document.getElementById('download-result').addEventListener('click', () =>
player.resultRecorder.download('result.p2lrn-result')
);
});
</script>For authenticated users, replace identity with platform mode and values supplied by your authorized host. The result must be downloaded through a user action or stored using an explicitly authorized workflow. It is never silently uploaded.
5. No platform and no internet: collect local results
- The teacher distributes compatible offline deliveries and gives each learner their own code.
- The learner enters the code, completes the activity and finishes to download their result file.
- The learner returns the
.p2lrn-resultby USB, an authorized shared folder or the agreed method. Results do not magically appear on the teacher's computer. - The teacher opens Analytics and chooses Open results to select multiple files, or Open folder where supported by the browser.
- Analytics checks each result, groups learner attempts and shows the metrics. No index, website or database is required for local import.
For fully disconnected use, the Analytics application assets themselves must also be available locally. A CDN script URL requires internet access unless the assets have been hosted locally.
6. Several learners with src: create the static index
When files are available on an authorized website, you can avoid an analytics endpoint. Put the results and a small JSON index into one folder. The filenames below are examples; use your actual names.
results/
result-001.p2lrn-result
result-002.p2lrn-result
index.p2lrn-results.jsonCreate index.p2lrn-results.json as follows:
{
"schema": "https://p2lrn.org/schema/analytics-results-index/1",
"results": [
"result-001.p2lrn-result",
"result-002.p2lrn-result"
]
}Each value in results is a path relative to the index file. For a file in the same folder, use its filename. Indexed result URLs must share the same origin as the index; external-origin entries are not allowed. A single import accepts up to 500 result files.
Then embed the panel:
<p2lrn-analytics id="panel.analytics"
src="/results/index.p2lrn-results.json"></p2lrn-analytics>Analytics fetches the list, checks every result and combines learners, courses, attempts and available scores. The index stores no results: it only lists the files to open.
7. Refresh the index when new results arrive
Manual method — no software installation
- Receive the new
.p2lrn-resultfiles and place them in the same folder with unique filenames. Do not overwrite earlier attempts. - Open
index.p2lrn-results.jsonwith a text editor. - Add the exact filename of each new result to the
resultsarray, separated by commas, while retaining earlier entries. - Save valid JSON and upload or copy the updated index and files to the authorized destination.
- Click Refresh in Analytics, or reload the page. If new entries do not appear, check HTTP caching of the index.
Automatic method with Node.js — no database
For larger batches, download the standalone results-index generator. Place it next to your results folder and run it whenever new results arrive:
node p2lrn-results-index.mjs ./resultsThe script reads only direct files ending in .p2lrn-result, checks schema and SHA-256, sorts filenames and regenerates the full index. If any file is invalid, the existing index remains unchanged. It does not upload anything. You still need to copy or publish the validated files and index to the protected destination.
8. How files reach Analytics: three real scenarios
Learners download and hand in their files. The teacher collects them on their computer and imports them locally into Analytics. No src URL or server is necessary.
An authorized person places result files on protected web storage and maintains the index. Analytics reads them via src; the website does not automatically discover or receive files.
The host application may store each result in access-controlled file storage and update the index for the teacher. This delivery and permission workflow must be integrated into the host, but a database is not mandatory.
Privacy: never place identifiable learner results in an openly accessible web directory. A public JavaScript CDN does not provide access control for result files. If private hosting is unavailable, use local file import.
9. Identity, integrity and limits
Analytics associates files using the declared identity, course ID (assignment.packageId) and attempt ID (attempt.id). Legacy files may have ambiguous identifiers if the same old token was shared. Identical result files are deduplicated; incompatible or altered results are rejected.
SHA-256 is not a digital signature: it detects a mismatch with the embedded digest, but anyone who rewrites the file can recompute a new digest. Neither a typed code nor a mode:'platform' field by itself proves who performed the activity. Local scores remain informative until an authorized system confirms official grades.
10. Common issues and fixes
That is not a results source. Use a .p2lrn-result file or a .p2lrn-results.json index.
Check the URL, authenticated session, file permissions and server headers. The index does not replace access control.
Check that their files were received, added to the index and loaded by refreshing the panel. Index updates are not automatic.
Ensure that learner codes differ, or that the platform issuer and subject identifier are stable and correctly assigned.
Do not hand-edit results. Obtain the original Player-generated file again; it is intentionally rejected.
Use multi-file import or the static index. A single result URL represents a single result file.
11. NPM and advanced integration
The NPM Analytics guide explains package installation, Player result capture through JavaScript and file-index generation in a web application.
Authenticated JSON metrics endpoints, panel.data and panel.dataSource remain available for more complex host integrations, but are optional for the .p2lrn-result file workflow.
12. Player and Analytics on your own platform
Authenticated learners do not download or manually return results. Player automatically creates the same .p2lrn-result format with in_progress and later submitted status. The Host stores it privately, separate from course files, and Analytics reads a teacher-authorized result index. No additional database is required.
import {createPlayerAnalyticsSession,createHttpResultHost} from '@p2lrn/player';
const host=createHttpResultHost({baseUrl:'/api/p2lrn/results',csrfToken:()=>platform.csrfToken});
const player=document.createElement('p2lrn-player');
const session=await createPlayerAnalyticsSession(player,{identity:{mode:'platform',issuer:user.issuer,subjectId:user.id},courseId:'REAL_COURSE_ID',host});
// Attach the Host before Player mounts so saved activity states are restored.
player.setAttribute('src','/courses/course.p2lrn');
document.querySelector('#player').replaceChildren(player);
// Call session.finish() when the learner completes the course.const panel=document.getElementById('panel.analytics');
panel.src=host.indexUrl('REAL_COURSE_ID');
await panel.refresh();The reference backend is exposed as @p2lrn/player/server/results. Your own platform must provide authenticate, authorize and checkCsrf and a private persistent writable directory. Course storage remains read-only. If saving fails, Player receives an error rather than false confirmation. The Host must verify authenticated learner identities and course permissions and must never expose student result files publicly.
See spec/RESULTS-ANALYTICS.md in the repository. These APIs are present in the integration source; check that the published npm/CDN version includes the contract before relying on it.