@patch-adams/core 1.4.2 → 1.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +519 -80
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +519 -80
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +519 -80
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +519 -80
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1047,48 +1047,55 @@ function generateLrsBridgeCode(options) {
|
|
|
1047
1047
|
|
|
1048
1048
|
function setupBridge() {
|
|
1049
1049
|
try {
|
|
1050
|
-
// 1. Try PlayerIntegration first (Xyleme native mode)
|
|
1050
|
+
// 1. Try PlayerIntegration first (Xyleme native mode) - for SCORM APIs
|
|
1051
1051
|
var integrationResult = findPlayerIntegration(10);
|
|
1052
1052
|
if (integrationResult) {
|
|
1053
1053
|
LRS.integration = integrationResult.integration;
|
|
1054
1054
|
LRS.integrationLevel = integrationResult.level;
|
|
1055
|
-
|
|
1056
|
-
log('Using PlayerIntegration mode');
|
|
1055
|
+
log('Found PlayerIntegration at level', integrationResult.level);
|
|
1057
1056
|
|
|
1058
1057
|
if (LRS.integration.Internal_SCORM_2004_API) {
|
|
1059
1058
|
window.API_1484_11 = LRS.integration.Internal_SCORM_2004_API;
|
|
1060
1059
|
LRS.scormApi = window.API_1484_11;
|
|
1060
|
+
LRS.scormApiFound = true;
|
|
1061
|
+
LRS.scormApiType = '2004';
|
|
1061
1062
|
}
|
|
1062
1063
|
if (LRS.integration.Internal_SCORM_12_API) {
|
|
1063
1064
|
window.API = LRS.integration.Internal_SCORM_12_API;
|
|
1065
|
+
if (!LRS.scormApiFound) {
|
|
1066
|
+
LRS.scormApi = window.API;
|
|
1067
|
+
LRS.scormApiFound = true;
|
|
1068
|
+
LRS.scormApiType = '1.2';
|
|
1069
|
+
}
|
|
1064
1070
|
}
|
|
1065
|
-
return
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
// 2. Search for SCORM APIs
|
|
1069
|
-
log('No PlayerIntegration found, searching for SCORM APIs...');
|
|
1070
|
-
|
|
1071
|
-
var api2004 = findAPIInFrameHierarchy('API_1484_11', 10);
|
|
1072
|
-
if (api2004) {
|
|
1073
|
-
LRS.scormApi = api2004.api;
|
|
1074
|
-
LRS.scormApiLevel = api2004.level;
|
|
1075
|
-
LRS.scormApiWindow = api2004.window;
|
|
1076
|
-
LRS.scormApiFound = true;
|
|
1077
|
-
LRS.scormApiType = '2004';
|
|
1078
|
-
log('Found SCORM 2004 API at level ' + api2004.level);
|
|
1071
|
+
// Don't return early - continue to detect LRS endpoint for direct xAPI
|
|
1079
1072
|
} else {
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1073
|
+
// 2. Search for SCORM APIs directly
|
|
1074
|
+
log('No PlayerIntegration found, searching for SCORM APIs...');
|
|
1075
|
+
|
|
1076
|
+
var api2004 = findAPIInFrameHierarchy('API_1484_11', 10);
|
|
1077
|
+
if (api2004) {
|
|
1078
|
+
LRS.scormApi = api2004.api;
|
|
1079
|
+
LRS.scormApiLevel = api2004.level;
|
|
1080
|
+
LRS.scormApiWindow = api2004.window;
|
|
1085
1081
|
LRS.scormApiFound = true;
|
|
1086
|
-
LRS.scormApiType = '
|
|
1087
|
-
log('Found SCORM
|
|
1082
|
+
LRS.scormApiType = '2004';
|
|
1083
|
+
log('Found SCORM 2004 API at level ' + api2004.level);
|
|
1084
|
+
} else {
|
|
1085
|
+
var api12 = findAPIInFrameHierarchy('API', 10);
|
|
1086
|
+
if (api12) {
|
|
1087
|
+
LRS.scormApi = api12.api;
|
|
1088
|
+
LRS.scormApiLevel = api12.level;
|
|
1089
|
+
LRS.scormApiWindow = api12.window;
|
|
1090
|
+
LRS.scormApiFound = true;
|
|
1091
|
+
LRS.scormApiType = '1.2';
|
|
1092
|
+
log('Found SCORM 1.2 API at level ' + api12.level);
|
|
1093
|
+
}
|
|
1088
1094
|
}
|
|
1089
1095
|
}
|
|
1090
1096
|
|
|
1091
|
-
// 3.
|
|
1097
|
+
// 3. ALWAYS try to determine LRS endpoint for direct xAPI statements
|
|
1098
|
+
// This is critical for detailed tracking (media, interactions, quizzes)
|
|
1092
1099
|
// Priority: 1) Configured endpoint, 2) Auto-detected Bravais endpoint
|
|
1093
1100
|
if (LRS_ENDPOINT_CONFIG && LRS_ENDPOINT_CONFIG.length > 0) {
|
|
1094
1101
|
LRS_ENDPOINT = LRS_ENDPOINT_CONFIG;
|
|
@@ -1104,9 +1111,17 @@ function generateLrsBridgeCode(options) {
|
|
|
1104
1111
|
LRS.lrsEndpoint = LRS_ENDPOINT;
|
|
1105
1112
|
|
|
1106
1113
|
// 4. Determine mode based on what we found
|
|
1114
|
+
// Prefer directLRS when available for detailed tracking
|
|
1107
1115
|
if (LRS_ENDPOINT && LRS_ENDPOINT.length > 0) {
|
|
1108
1116
|
LRS.mode = 'directLRS';
|
|
1109
1117
|
log('Using direct LRS mode with endpoint:', LRS_ENDPOINT);
|
|
1118
|
+
if (LRS.integration) {
|
|
1119
|
+
log('PlayerIntegration also available for SCORM state');
|
|
1120
|
+
}
|
|
1121
|
+
return true;
|
|
1122
|
+
} else if (LRS.integration) {
|
|
1123
|
+
LRS.mode = 'playerIntegration';
|
|
1124
|
+
log('Using PlayerIntegration mode (no direct LRS endpoint)');
|
|
1110
1125
|
return true;
|
|
1111
1126
|
} else if (LRS.scormApiFound) {
|
|
1112
1127
|
LRS.mode = 'scormOnly';
|
|
@@ -2325,6 +2340,154 @@ function generateLrsBridgeCode(options) {
|
|
|
2325
2340
|
return info;
|
|
2326
2341
|
}
|
|
2327
2342
|
|
|
2343
|
+
// ========================================================================
|
|
2344
|
+
// 4b. LESSON/SECTION NAME EXTRACTION
|
|
2345
|
+
// ========================================================================
|
|
2346
|
+
|
|
2347
|
+
/**
|
|
2348
|
+
* Get the current lesson/section information from Rise DOM
|
|
2349
|
+
* Returns { id, name, lessonIndex, sectionName }
|
|
2350
|
+
*/
|
|
2351
|
+
function getCurrentLessonInfo() {
|
|
2352
|
+
var lessonInfo = {
|
|
2353
|
+
id: window.location.hash || '#/',
|
|
2354
|
+
name: null,
|
|
2355
|
+
lessonIndex: null,
|
|
2356
|
+
sectionName: null
|
|
2357
|
+
};
|
|
2358
|
+
|
|
2359
|
+
try {
|
|
2360
|
+
// 1. Try to get from Rise's navigation sidebar
|
|
2361
|
+
// Rise shows active lesson with specific classes
|
|
2362
|
+
var activeLesson = document.querySelector(
|
|
2363
|
+
'.sidebar__link--current, ' +
|
|
2364
|
+
'.sidebar-lesson--active, ' +
|
|
2365
|
+
'.nav-sidebar__lesson--active, ' +
|
|
2366
|
+
'[class*="sidebar"] [class*="current"], ' +
|
|
2367
|
+
'[class*="sidebar"] [class*="active"], ' +
|
|
2368
|
+
'[aria-current="page"], ' +
|
|
2369
|
+
'[aria-current="true"]'
|
|
2370
|
+
);
|
|
2371
|
+
|
|
2372
|
+
if (activeLesson) {
|
|
2373
|
+
var lessonTitle = activeLesson.querySelector(
|
|
2374
|
+
'.sidebar__link-text, ' +
|
|
2375
|
+
'.lesson-title, ' +
|
|
2376
|
+
'.sidebar-lesson__title, ' +
|
|
2377
|
+
'[class*="title"], ' +
|
|
2378
|
+
'[class*="label"]'
|
|
2379
|
+
);
|
|
2380
|
+
if (lessonTitle) {
|
|
2381
|
+
lessonInfo.name = lessonTitle.textContent.trim();
|
|
2382
|
+
} else {
|
|
2383
|
+
lessonInfo.name = activeLesson.textContent.trim();
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
// Try to get the section/module name (parent group)
|
|
2387
|
+
var section = activeLesson.closest(
|
|
2388
|
+
'.sidebar__section, ' +
|
|
2389
|
+
'.sidebar-section, ' +
|
|
2390
|
+
'[class*="section"], ' +
|
|
2391
|
+
'[class*="module"]'
|
|
2392
|
+
);
|
|
2393
|
+
if (section) {
|
|
2394
|
+
var sectionTitle = section.querySelector(
|
|
2395
|
+
'.sidebar__section-title, ' +
|
|
2396
|
+
'.section-title, ' +
|
|
2397
|
+
'[class*="section-title"], ' +
|
|
2398
|
+
'[class*="module-title"], ' +
|
|
2399
|
+
'h2, h3'
|
|
2400
|
+
);
|
|
2401
|
+
if (sectionTitle) {
|
|
2402
|
+
lessonInfo.sectionName = sectionTitle.textContent.trim();
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
// 2. Try Rise's header/breadcrumb
|
|
2408
|
+
if (!lessonInfo.name) {
|
|
2409
|
+
var headerTitle = document.querySelector(
|
|
2410
|
+
'.lesson-header__title, ' +
|
|
2411
|
+
'.lesson__title, ' +
|
|
2412
|
+
'.content-header__title, ' +
|
|
2413
|
+
'[class*="lesson"] [class*="header"] [class*="title"], ' +
|
|
2414
|
+
'.blocks-lesson-header__title'
|
|
2415
|
+
);
|
|
2416
|
+
if (headerTitle) {
|
|
2417
|
+
lessonInfo.name = headerTitle.textContent.trim();
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
// 3. Try breadcrumbs
|
|
2422
|
+
if (!lessonInfo.name || !lessonInfo.sectionName) {
|
|
2423
|
+
var breadcrumbs = document.querySelectorAll(
|
|
2424
|
+
'.breadcrumb__item, ' +
|
|
2425
|
+
'.breadcrumbs li, ' +
|
|
2426
|
+
'[class*="breadcrumb"] span, ' +
|
|
2427
|
+
'[class*="breadcrumb"] a'
|
|
2428
|
+
);
|
|
2429
|
+
if (breadcrumbs.length >= 2) {
|
|
2430
|
+
// Usually: Course > Section > Lesson
|
|
2431
|
+
if (!lessonInfo.sectionName && breadcrumbs.length >= 2) {
|
|
2432
|
+
lessonInfo.sectionName = breadcrumbs[breadcrumbs.length - 2].textContent.trim();
|
|
2433
|
+
}
|
|
2434
|
+
if (!lessonInfo.name) {
|
|
2435
|
+
lessonInfo.name = breadcrumbs[breadcrumbs.length - 1].textContent.trim();
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
// 4. Try Rise's internal data
|
|
2441
|
+
if (window.__RISE_COURSE_DATA__ && window.__RISE_COURSE_DATA__.lessons) {
|
|
2442
|
+
var lessonId = lessonInfo.id.replace('#/lessons/', '').replace('#/', '');
|
|
2443
|
+
var lessons = window.__RISE_COURSE_DATA__.lessons;
|
|
2444
|
+
for (var i = 0; i < lessons.length; i++) {
|
|
2445
|
+
if (lessons[i].id === lessonId || lessons[i].slug === lessonId) {
|
|
2446
|
+
lessonInfo.name = lessons[i].title || lessonInfo.name;
|
|
2447
|
+
lessonInfo.lessonIndex = i;
|
|
2448
|
+
break;
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
// 5. Clean up - remove any "Home" or generic names if we're on a real lesson
|
|
2454
|
+
if (lessonInfo.name) {
|
|
2455
|
+
lessonInfo.name = lessonInfo.name.replace(/^\\d+\\.\\s*/, ''); // Remove leading numbers "1. "
|
|
2456
|
+
if (lessonInfo.name.length > 200) {
|
|
2457
|
+
lessonInfo.name = lessonInfo.name.substring(0, 200) + '...';
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
if (lessonInfo.sectionName) {
|
|
2462
|
+
lessonInfo.sectionName = lessonInfo.sectionName.replace(/^\\d+\\.\\s*/, '');
|
|
2463
|
+
if (lessonInfo.sectionName.length > 200) {
|
|
2464
|
+
lessonInfo.sectionName = lessonInfo.sectionName.substring(0, 200) + '...';
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
} catch (e) {
|
|
2469
|
+
warn('Error extracting lesson info:', e);
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
log('Current lesson info:', lessonInfo);
|
|
2473
|
+
return lessonInfo;
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
/**
|
|
2477
|
+
* Cache the current lesson info (updates on navigation)
|
|
2478
|
+
*/
|
|
2479
|
+
var cachedLessonInfo = null;
|
|
2480
|
+
var cachedLessonHash = null;
|
|
2481
|
+
|
|
2482
|
+
function getCachedLessonInfo() {
|
|
2483
|
+
var currentHash = window.location.hash;
|
|
2484
|
+
if (cachedLessonHash !== currentHash) {
|
|
2485
|
+
cachedLessonInfo = getCurrentLessonInfo();
|
|
2486
|
+
cachedLessonHash = currentHash;
|
|
2487
|
+
}
|
|
2488
|
+
return cachedLessonInfo || getCurrentLessonInfo();
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2328
2491
|
// ========================================================================
|
|
2329
2492
|
// 5. XAPI STATEMENT BUILDING
|
|
2330
2493
|
// ========================================================================
|
|
@@ -2669,14 +2832,20 @@ function generateLrsBridgeCode(options) {
|
|
|
2669
2832
|
LRS.eventLog.push({
|
|
2670
2833
|
statement: statement,
|
|
2671
2834
|
timestamp: new Date().toISOString(),
|
|
2672
|
-
mode: LRS.mode
|
|
2835
|
+
mode: LRS.mode,
|
|
2836
|
+
lrsEndpoint: LRS_ENDPOINT
|
|
2673
2837
|
});
|
|
2674
2838
|
|
|
2675
|
-
//
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2839
|
+
// ALWAYS prefer direct LRS when endpoint is available
|
|
2840
|
+
// This ensures all our detailed tracking (media, interactions, etc.) goes to the LRS
|
|
2841
|
+
// PlayerIntegration is used only for SCORM state, not for xAPI statements
|
|
2842
|
+
if (LRS_ENDPOINT && LRS_ENDPOINT.length > 0) {
|
|
2843
|
+
log('Sending via directLRS:', LRS_ENDPOINT);
|
|
2679
2844
|
return sendViaDirectLRS(statement);
|
|
2845
|
+
} else if (LRS.mode === 'playerIntegration' && LRS.integration) {
|
|
2846
|
+
// Fallback to PlayerIntegration only if no direct LRS endpoint
|
|
2847
|
+
log('No LRS endpoint, falling back to PlayerIntegration');
|
|
2848
|
+
return sendViaPlayerIntegration(statement);
|
|
2680
2849
|
} else {
|
|
2681
2850
|
// SCORM-only or offline mode - just log
|
|
2682
2851
|
log('Statement logged (no LRS):', statement.verb.display['en-US']);
|
|
@@ -2815,13 +2984,16 @@ function generateLrsBridgeCode(options) {
|
|
|
2815
2984
|
LRS.contentOpened = function(data) {
|
|
2816
2985
|
if (!TRACK_NAVIGATION) return;
|
|
2817
2986
|
|
|
2987
|
+
// Get current lesson info from DOM (includes name)
|
|
2988
|
+
var lessonInfo = getCurrentLessonInfo();
|
|
2989
|
+
|
|
2818
2990
|
var result = null;
|
|
2819
2991
|
if (data.duration) {
|
|
2820
2992
|
result = { duration: data.duration };
|
|
2821
2993
|
}
|
|
2822
2994
|
|
|
2823
2995
|
// Extract clean page/lesson ID from Rise hash paths
|
|
2824
|
-
var lessonId = data.pageGuid || data.lessonId || '';
|
|
2996
|
+
var lessonId = data.pageGuid || data.lessonId || lessonInfo.id || '';
|
|
2825
2997
|
if (lessonId.indexOf('#/lessons/') === 0) {
|
|
2826
2998
|
lessonId = lessonId.substring(10); // Remove '#/lessons/'
|
|
2827
2999
|
} else if (lessonId.indexOf('#/') === 0) {
|
|
@@ -2832,7 +3004,9 @@ function generateLrsBridgeCode(options) {
|
|
|
2832
3004
|
var additionalContext = {
|
|
2833
3005
|
extensions: {
|
|
2834
3006
|
pageId: lessonId || 'home',
|
|
2835
|
-
pageTitle: data.title || 'Page',
|
|
3007
|
+
pageTitle: data.title || lessonInfo.name || 'Page',
|
|
3008
|
+
lessonName: lessonInfo.name,
|
|
3009
|
+
sectionName: lessonInfo.sectionName,
|
|
2836
3010
|
pageUrl: data.url || window.location.href
|
|
2837
3011
|
}
|
|
2838
3012
|
};
|
|
@@ -2842,7 +3016,12 @@ function generateLrsBridgeCode(options) {
|
|
|
2842
3016
|
var statement = buildStatement(
|
|
2843
3017
|
'experienced',
|
|
2844
3018
|
'http://xyleme.com/bravais/activities/document',
|
|
2845
|
-
{
|
|
3019
|
+
{
|
|
3020
|
+
event: 'page_viewed',
|
|
3021
|
+
pageId: lessonId,
|
|
3022
|
+
lessonName: lessonInfo.name,
|
|
3023
|
+
sectionName: lessonInfo.sectionName
|
|
3024
|
+
},
|
|
2846
3025
|
result,
|
|
2847
3026
|
additionalContext
|
|
2848
3027
|
);
|
|
@@ -2853,6 +3032,9 @@ function generateLrsBridgeCode(options) {
|
|
|
2853
3032
|
LRS.mediaPlayed = function(data) {
|
|
2854
3033
|
if (!TRACK_MEDIA) return;
|
|
2855
3034
|
|
|
3035
|
+
// Get current lesson context
|
|
3036
|
+
var lessonInfo = getCachedLessonInfo();
|
|
3037
|
+
|
|
2856
3038
|
var mediaType = data.type === 'audio' ? ACTIVITY_TYPES.audio : ACTIVITY_TYPES.video;
|
|
2857
3039
|
var verbKey = data.action === 'play' ? 'played' :
|
|
2858
3040
|
data.action === 'pause' ? 'paused' :
|
|
@@ -2863,7 +3045,11 @@ function generateLrsBridgeCode(options) {
|
|
|
2863
3045
|
mediaSrc: data.src || 'media-' + Date.now(),
|
|
2864
3046
|
mediaName: data.name || (data.type || 'Media'),
|
|
2865
3047
|
mediaType: data.type || 'video',
|
|
2866
|
-
'https://w3id.org/xapi/video/extensions/length': data.duration || 0
|
|
3048
|
+
'https://w3id.org/xapi/video/extensions/length': data.duration || 0,
|
|
3049
|
+
// Include lesson/section context
|
|
3050
|
+
lessonId: lessonInfo.id,
|
|
3051
|
+
lessonName: lessonInfo.name,
|
|
3052
|
+
sectionName: lessonInfo.sectionName
|
|
2867
3053
|
};
|
|
2868
3054
|
|
|
2869
3055
|
var result = {
|
|
@@ -2886,12 +3072,21 @@ function generateLrsBridgeCode(options) {
|
|
|
2886
3072
|
LRS.assessmentStarted = function(data) {
|
|
2887
3073
|
if (!TRACK_QUIZZES) return;
|
|
2888
3074
|
|
|
3075
|
+
// Get lesson context
|
|
3076
|
+
var lessonInfo = getCachedLessonInfo();
|
|
3077
|
+
|
|
2889
3078
|
// Build assessment activity object for Xyleme format
|
|
2890
3079
|
var assessmentObject = buildAssessmentActivityObject({
|
|
2891
3080
|
assessmentGuid: data.assessmentGuid || data.id,
|
|
2892
|
-
title: data.title || 'Assessment'
|
|
3081
|
+
title: data.title || data.assessmentName || 'Assessment'
|
|
2893
3082
|
});
|
|
2894
3083
|
|
|
3084
|
+
// Add lesson context to extensions
|
|
3085
|
+
assessmentObject.definition = assessmentObject.definition || {};
|
|
3086
|
+
assessmentObject.definition.extensions = assessmentObject.definition.extensions || {};
|
|
3087
|
+
assessmentObject.definition.extensions.lessonName = lessonInfo.name;
|
|
3088
|
+
assessmentObject.definition.extensions.sectionName = lessonInfo.sectionName;
|
|
3089
|
+
|
|
2895
3090
|
var statement = buildStatementXyleme('attempted', assessmentObject);
|
|
2896
3091
|
sendStatement(statement);
|
|
2897
3092
|
};
|
|
@@ -2900,13 +3095,31 @@ function generateLrsBridgeCode(options) {
|
|
|
2900
3095
|
LRS.assessmentEnded = function(data) {
|
|
2901
3096
|
if (!TRACK_QUIZZES) return;
|
|
2902
3097
|
|
|
3098
|
+
// Get lesson context (may be passed in from extractQuizResult)
|
|
3099
|
+
var lessonInfo = data.lessonName ? { name: data.lessonName, sectionName: data.sectionName } : getCachedLessonInfo();
|
|
3100
|
+
|
|
2903
3101
|
// Build assessment activity object for Xyleme format
|
|
2904
3102
|
var assessmentObject = buildAssessmentActivityObject({
|
|
2905
|
-
assessmentGuid: data.assessmentGuid || data.id,
|
|
2906
|
-
title: data.title || 'Assessment'
|
|
3103
|
+
assessmentGuid: data.assessmentGuid || data.assessmentId || data.id,
|
|
3104
|
+
title: data.title || data.assessmentName || 'Assessment'
|
|
2907
3105
|
});
|
|
2908
3106
|
|
|
2909
|
-
|
|
3107
|
+
// Add lesson context to extensions
|
|
3108
|
+
assessmentObject.definition = assessmentObject.definition || {};
|
|
3109
|
+
assessmentObject.definition.extensions = assessmentObject.definition.extensions || {};
|
|
3110
|
+
assessmentObject.definition.extensions.lessonName = lessonInfo.name;
|
|
3111
|
+
assessmentObject.definition.extensions.sectionName = lessonInfo.sectionName;
|
|
3112
|
+
assessmentObject.definition.extensions.assessmentName = data.assessmentName || data.title;
|
|
3113
|
+
|
|
3114
|
+
var result = {
|
|
3115
|
+
completion: true,
|
|
3116
|
+
extensions: {
|
|
3117
|
+
'https://patch-adams.io/xapi/extensions/assessmentName': data.assessmentName || data.title || null,
|
|
3118
|
+
'https://patch-adams.io/xapi/extensions/lessonName': lessonInfo.name || null,
|
|
3119
|
+
'https://patch-adams.io/xapi/extensions/sectionName': lessonInfo.sectionName || null,
|
|
3120
|
+
'https://patch-adams.io/xapi/extensions/questionCount': data.questionCount || null
|
|
3121
|
+
}
|
|
3122
|
+
};
|
|
2910
3123
|
|
|
2911
3124
|
if (typeof data.score === 'number') {
|
|
2912
3125
|
result.score = {
|
|
@@ -2935,14 +3148,27 @@ function generateLrsBridgeCode(options) {
|
|
|
2935
3148
|
LRS.questionAnswered = function(data) {
|
|
2936
3149
|
if (!TRACK_QUIZZES) return;
|
|
2937
3150
|
|
|
3151
|
+
// Get lesson context if not provided
|
|
3152
|
+
var lessonInfo = data.lessonName ? data : getCachedLessonInfo();
|
|
3153
|
+
|
|
2938
3154
|
// Build question activity object for Xyleme format
|
|
2939
3155
|
var questionObject = buildQuestionActivityObject({
|
|
2940
3156
|
questionGuid: data.questionGuid || data.questionId,
|
|
2941
3157
|
text: data.questionText || 'Question'
|
|
2942
3158
|
});
|
|
2943
3159
|
|
|
3160
|
+
// Build comprehensive result with human-readable data
|
|
2944
3161
|
var result = {
|
|
2945
|
-
response: data.answer || data.response || ''
|
|
3162
|
+
response: data.answer || data.response || '',
|
|
3163
|
+
extensions: {
|
|
3164
|
+
'https://patch-adams.io/xapi/extensions/questionNumber': data.questionNumber || null,
|
|
3165
|
+
'https://patch-adams.io/xapi/extensions/questionText': data.questionText || null,
|
|
3166
|
+
'https://patch-adams.io/xapi/extensions/answerText': data.answer || data.response || null,
|
|
3167
|
+
'https://patch-adams.io/xapi/extensions/correctAnswer': data.correctAnswer || null,
|
|
3168
|
+
'https://patch-adams.io/xapi/extensions/assessmentName': data.assessmentName || null,
|
|
3169
|
+
'https://patch-adams.io/xapi/extensions/lessonName': lessonInfo.lessonName || lessonInfo.name || null,
|
|
3170
|
+
'https://patch-adams.io/xapi/extensions/sectionName': lessonInfo.sectionName || null
|
|
3171
|
+
}
|
|
2946
3172
|
};
|
|
2947
3173
|
|
|
2948
3174
|
if (data.result === 'correct' || data.correct === true) {
|
|
@@ -2974,6 +3200,9 @@ function generateLrsBridgeCode(options) {
|
|
|
2974
3200
|
LRS.interacted = function(data) {
|
|
2975
3201
|
if (!TRACK_INTERACTIONS) return;
|
|
2976
3202
|
|
|
3203
|
+
// Get current lesson context
|
|
3204
|
+
var lessonInfo = getCachedLessonInfo();
|
|
3205
|
+
|
|
2977
3206
|
// Activity type is interaction, details include interaction ID and type
|
|
2978
3207
|
var statement = buildStatement(
|
|
2979
3208
|
'interacted',
|
|
@@ -2981,7 +3210,11 @@ function generateLrsBridgeCode(options) {
|
|
|
2981
3210
|
{
|
|
2982
3211
|
interactionId: data.id || 'interaction-' + Date.now(),
|
|
2983
3212
|
interactionName: data.name || data.type || 'Interaction',
|
|
2984
|
-
interactionType: data.interactionType || data.type || 'other'
|
|
3213
|
+
interactionType: data.interactionType || data.type || 'other',
|
|
3214
|
+
// Include lesson/section context
|
|
3215
|
+
lessonId: lessonInfo.id,
|
|
3216
|
+
lessonName: lessonInfo.name,
|
|
3217
|
+
sectionName: lessonInfo.sectionName
|
|
2985
3218
|
}
|
|
2986
3219
|
);
|
|
2987
3220
|
sendStatement(statement);
|
|
@@ -3062,49 +3295,146 @@ function generateLrsBridgeCode(options) {
|
|
|
3062
3295
|
function setupMediaInterceptors() {
|
|
3063
3296
|
if (!TRACK_MEDIA) return;
|
|
3064
3297
|
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3298
|
+
// Track which elements we've already attached listeners to
|
|
3299
|
+
var trackedMedia = new WeakSet();
|
|
3300
|
+
|
|
3301
|
+
// Helper to get media name/title from element or surrounding context
|
|
3302
|
+
function getMediaName(el) {
|
|
3303
|
+
// Check element attributes
|
|
3304
|
+
var name = el.title || el.getAttribute('aria-label') || el.getAttribute('data-name');
|
|
3305
|
+
if (name) return name;
|
|
3306
|
+
|
|
3307
|
+
// Check parent Rise video block for title
|
|
3308
|
+
var videoBlock = findClosest(el, '[data-block-type="video"], .blocks-video, .video-block');
|
|
3309
|
+
if (videoBlock) {
|
|
3310
|
+
var titleEl = videoBlock.querySelector('.video-title, .block-title, [class*="title"]');
|
|
3311
|
+
if (titleEl) return titleEl.textContent.trim();
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
// Use src filename as fallback
|
|
3315
|
+
var src = el.src || el.currentSrc || '';
|
|
3316
|
+
if (src) {
|
|
3317
|
+
var filename = src.split('/').pop().split('?')[0];
|
|
3318
|
+
if (filename && filename.length < 100) return filename;
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
return el.tagName.toLowerCase();
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
// Attach media event listeners to a single element
|
|
3325
|
+
function attachMediaListeners(el) {
|
|
3326
|
+
if (trackedMedia.has(el)) return;
|
|
3327
|
+
trackedMedia.add(el);
|
|
3328
|
+
|
|
3329
|
+
var mediaType = el.tagName.toLowerCase();
|
|
3330
|
+
log('Attaching media listeners to:', mediaType, el.src || el.currentSrc || 'no-src');
|
|
3331
|
+
|
|
3332
|
+
el.addEventListener('play', function() {
|
|
3333
|
+
log('Media play event captured:', mediaType);
|
|
3068
3334
|
LRS.mediaPlayed({
|
|
3069
|
-
type:
|
|
3070
|
-
src:
|
|
3071
|
-
name:
|
|
3072
|
-
currentTime:
|
|
3073
|
-
duration:
|
|
3335
|
+
type: mediaType,
|
|
3336
|
+
src: el.src || el.currentSrc || '',
|
|
3337
|
+
name: getMediaName(el),
|
|
3338
|
+
currentTime: el.currentTime || 0,
|
|
3339
|
+
duration: el.duration || 0,
|
|
3074
3340
|
action: 'play'
|
|
3075
3341
|
});
|
|
3076
|
-
}
|
|
3077
|
-
}, true);
|
|
3342
|
+
});
|
|
3078
3343
|
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3344
|
+
el.addEventListener('pause', function() {
|
|
3345
|
+
// Ignore pause events that fire right before ended
|
|
3346
|
+
if (el.ended) return;
|
|
3347
|
+
log('Media pause event captured:', mediaType);
|
|
3082
3348
|
LRS.mediaPlayed({
|
|
3083
|
-
type:
|
|
3084
|
-
src:
|
|
3085
|
-
name:
|
|
3086
|
-
currentTime:
|
|
3087
|
-
duration:
|
|
3349
|
+
type: mediaType,
|
|
3350
|
+
src: el.src || el.currentSrc || '',
|
|
3351
|
+
name: getMediaName(el),
|
|
3352
|
+
currentTime: el.currentTime || 0,
|
|
3353
|
+
duration: el.duration || 0,
|
|
3088
3354
|
action: 'pause'
|
|
3089
3355
|
});
|
|
3090
|
-
}
|
|
3091
|
-
}, true);
|
|
3356
|
+
});
|
|
3092
3357
|
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
if (target.tagName === 'VIDEO' || target.tagName === 'AUDIO') {
|
|
3358
|
+
el.addEventListener('ended', function() {
|
|
3359
|
+
log('Media ended event captured:', mediaType);
|
|
3096
3360
|
LRS.mediaPlayed({
|
|
3097
|
-
type:
|
|
3098
|
-
src:
|
|
3099
|
-
name:
|
|
3100
|
-
currentTime:
|
|
3101
|
-
duration:
|
|
3361
|
+
type: mediaType,
|
|
3362
|
+
src: el.src || el.currentSrc || '',
|
|
3363
|
+
name: getMediaName(el),
|
|
3364
|
+
currentTime: el.duration || 0,
|
|
3365
|
+
duration: el.duration || 0,
|
|
3102
3366
|
action: 'completed'
|
|
3103
3367
|
});
|
|
3368
|
+
});
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
// Scan for and attach listeners to all video/audio elements
|
|
3372
|
+
function scanForMedia(root) {
|
|
3373
|
+
var elements = (root || document).querySelectorAll('video, audio');
|
|
3374
|
+
log('Scanning for media elements, found:', elements.length);
|
|
3375
|
+
for (var i = 0; i < elements.length; i++) {
|
|
3376
|
+
attachMediaListeners(elements[i]);
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3380
|
+
// Initial scan
|
|
3381
|
+
scanForMedia();
|
|
3382
|
+
|
|
3383
|
+
// Document-level capture for any we might miss
|
|
3384
|
+
document.addEventListener('play', function(e) {
|
|
3385
|
+
var target = e.target;
|
|
3386
|
+
if (target.tagName === 'VIDEO' || target.tagName === 'AUDIO') {
|
|
3387
|
+
// Attach listeners if not already tracked
|
|
3388
|
+
if (!trackedMedia.has(target)) {
|
|
3389
|
+
log('Captured play from untracked media, attaching listeners');
|
|
3390
|
+
attachMediaListeners(target);
|
|
3391
|
+
}
|
|
3104
3392
|
}
|
|
3105
3393
|
}, true);
|
|
3106
3394
|
|
|
3107
|
-
|
|
3395
|
+
// MutationObserver to detect dynamically added video/audio elements
|
|
3396
|
+
var mediaObserver = new MutationObserver(function(mutations) {
|
|
3397
|
+
var needsScan = false;
|
|
3398
|
+
mutations.forEach(function(mutation) {
|
|
3399
|
+
mutation.addedNodes.forEach(function(node) {
|
|
3400
|
+
if (node.nodeType === 1) {
|
|
3401
|
+
if (node.tagName === 'VIDEO' || node.tagName === 'AUDIO') {
|
|
3402
|
+
attachMediaListeners(node);
|
|
3403
|
+
} else if (node.querySelectorAll) {
|
|
3404
|
+
// Check for nested video/audio
|
|
3405
|
+
var nested = node.querySelectorAll('video, audio');
|
|
3406
|
+
if (nested.length > 0) {
|
|
3407
|
+
needsScan = true;
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
});
|
|
3412
|
+
});
|
|
3413
|
+
if (needsScan) {
|
|
3414
|
+
scanForMedia();
|
|
3415
|
+
}
|
|
3416
|
+
});
|
|
3417
|
+
|
|
3418
|
+
// Start observing once DOM is ready
|
|
3419
|
+
function startMediaObserver() {
|
|
3420
|
+
if (document.body) {
|
|
3421
|
+
mediaObserver.observe(document.body, {
|
|
3422
|
+
childList: true,
|
|
3423
|
+
subtree: true
|
|
3424
|
+
});
|
|
3425
|
+
log('Media mutation observer started');
|
|
3426
|
+
} else {
|
|
3427
|
+
setTimeout(startMediaObserver, 100);
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
startMediaObserver();
|
|
3431
|
+
|
|
3432
|
+
// Periodic rescan for Rise lazy-loaded content
|
|
3433
|
+
setInterval(function() {
|
|
3434
|
+
scanForMedia();
|
|
3435
|
+
}, 3000);
|
|
3436
|
+
|
|
3437
|
+
log('Media interceptors set up (enhanced)');
|
|
3108
3438
|
}
|
|
3109
3439
|
|
|
3110
3440
|
function setupNavigationInterceptors() {
|
|
@@ -3185,43 +3515,152 @@ function generateLrsBridgeCode(options) {
|
|
|
3185
3515
|
'quiz-' + Date.now();
|
|
3186
3516
|
}
|
|
3187
3517
|
|
|
3518
|
+
/**
|
|
3519
|
+
* Extract quiz/assessment name from the DOM
|
|
3520
|
+
*/
|
|
3521
|
+
function extractAssessmentName(node) {
|
|
3522
|
+
// Try various selectors for assessment/quiz titles
|
|
3523
|
+
var titleEl = node.querySelector(
|
|
3524
|
+
'.quiz-title, .assessment-title, .knowledge-check-title, ' +
|
|
3525
|
+
'.blocks-quiz__title, .block-title, ' +
|
|
3526
|
+
'[class*="quiz"] [class*="title"], ' +
|
|
3527
|
+
'[class*="assessment"] [class*="title"], ' +
|
|
3528
|
+
'h2, h3'
|
|
3529
|
+
);
|
|
3530
|
+
if (titleEl) {
|
|
3531
|
+
return titleEl.textContent.trim().substring(0, 200);
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3534
|
+
// Try to get from parent block
|
|
3535
|
+
var parentBlock = node.closest('[data-block-type]');
|
|
3536
|
+
if (parentBlock) {
|
|
3537
|
+
var blockTitle = parentBlock.querySelector('.block-title, h2, h3');
|
|
3538
|
+
if (blockTitle) {
|
|
3539
|
+
return blockTitle.textContent.trim().substring(0, 200);
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
|
|
3543
|
+
// Get current lesson name as context
|
|
3544
|
+
var lessonInfo = getCachedLessonInfo();
|
|
3545
|
+
if (lessonInfo.name) {
|
|
3546
|
+
return 'Quiz in ' + lessonInfo.name;
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3549
|
+
return 'Knowledge Check';
|
|
3550
|
+
}
|
|
3551
|
+
|
|
3188
3552
|
function extractQuizResult(node) {
|
|
3189
3553
|
var scoreEl = node.querySelector('.score-value, .quiz-score, .score-percentage, [class*="score"]');
|
|
3190
3554
|
var score = scoreEl ? parseFloat(scoreEl.textContent.replace(/[^0-9.]/g, '')) : null;
|
|
3191
3555
|
|
|
3192
|
-
|
|
3556
|
+
// Get assessment name and lesson context
|
|
3557
|
+
var assessmentName = extractAssessmentName(node);
|
|
3558
|
+
var lessonInfo = getCachedLessonInfo();
|
|
3559
|
+
|
|
3560
|
+
// Find all question containers - Rise uses various structures
|
|
3561
|
+
var questions = node.querySelectorAll(
|
|
3562
|
+
'.question-result, .question-feedback, .question-item, ' +
|
|
3563
|
+
'.blocks-quiz__question, .quiz-question, ' +
|
|
3564
|
+
'[class*="question-"][class*="result"], ' +
|
|
3565
|
+
'[class*="question-"][class*="feedback"], ' +
|
|
3566
|
+
'[data-question-id]'
|
|
3567
|
+
);
|
|
3568
|
+
|
|
3569
|
+
log('Found', questions.length, 'question elements in quiz result');
|
|
3193
3570
|
|
|
3194
3571
|
if (questions.length > 0) {
|
|
3195
3572
|
questions.forEach(function(q, index) {
|
|
3573
|
+
// Determine if correct - check multiple indicators
|
|
3196
3574
|
var isCorrect = q.classList.contains('correct') ||
|
|
3197
|
-
q.
|
|
3198
|
-
q.
|
|
3575
|
+
q.classList.contains('is-correct') ||
|
|
3576
|
+
q.querySelector('.correct, .is-correct, [class*="correct"]') !== null ||
|
|
3577
|
+
q.getAttribute('data-correct') === 'true' ||
|
|
3578
|
+
q.getAttribute('data-result') === 'correct';
|
|
3199
3579
|
|
|
3580
|
+
// Get the question text - try multiple selectors
|
|
3200
3581
|
var questionText = '';
|
|
3201
|
-
var questionEl = q.querySelector(
|
|
3582
|
+
var questionEl = q.querySelector(
|
|
3583
|
+
'.question-text, .question-stem, .question-title, ' +
|
|
3584
|
+
'.blocks-quiz__question-text, .quiz-question__text, ' +
|
|
3585
|
+
'[class*="question-text"], [class*="question-stem"], ' +
|
|
3586
|
+
'[class*="prompt"], p:first-of-type'
|
|
3587
|
+
);
|
|
3202
3588
|
if (questionEl) {
|
|
3203
3589
|
questionText = questionEl.textContent.trim();
|
|
3204
3590
|
}
|
|
3205
3591
|
|
|
3592
|
+
// Get the learner's selected answer text
|
|
3206
3593
|
var answerText = '';
|
|
3207
|
-
var answerEl = q.querySelector(
|
|
3594
|
+
var answerEl = q.querySelector(
|
|
3595
|
+
'.selected-answer, .learner-response, .answer-text, ' +
|
|
3596
|
+
'.user-answer, .chosen-answer, .response-text, ' +
|
|
3597
|
+
'.blocks-quiz__response, .quiz-question__response, ' +
|
|
3598
|
+
'[class*="selected"], [class*="chosen"], [class*="response"], ' +
|
|
3599
|
+
'[class*="user-answer"]'
|
|
3600
|
+
);
|
|
3208
3601
|
if (answerEl) {
|
|
3209
3602
|
answerText = answerEl.textContent.trim();
|
|
3210
3603
|
}
|
|
3211
3604
|
|
|
3605
|
+
// If no specific answer element, try to find selected radio/checkbox label
|
|
3606
|
+
if (!answerText) {
|
|
3607
|
+
var selectedInput = q.querySelector('input:checked, [aria-checked="true"]');
|
|
3608
|
+
if (selectedInput) {
|
|
3609
|
+
var label = q.querySelector('label[for="' + selectedInput.id + '"]');
|
|
3610
|
+
if (label) {
|
|
3611
|
+
answerText = label.textContent.trim();
|
|
3612
|
+
} else {
|
|
3613
|
+
var parentLabel = selectedInput.closest('label');
|
|
3614
|
+
if (parentLabel) {
|
|
3615
|
+
answerText = parentLabel.textContent.trim();
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
|
|
3621
|
+
// Get question ID if available
|
|
3622
|
+
var questionId = q.getAttribute('data-question-id') ||
|
|
3623
|
+
q.getAttribute('data-block-id') ||
|
|
3624
|
+
q.getAttribute('id') ||
|
|
3625
|
+
'q' + (index + 1);
|
|
3626
|
+
|
|
3627
|
+
// Get correct answer text if available (for reporting)
|
|
3628
|
+
var correctAnswerText = '';
|
|
3629
|
+
var correctEl = q.querySelector(
|
|
3630
|
+
'.correct-answer, .right-answer, ' +
|
|
3631
|
+
'[class*="correct-answer"], [class*="right-answer"]'
|
|
3632
|
+
);
|
|
3633
|
+
if (correctEl) {
|
|
3634
|
+
correctAnswerText = correctEl.textContent.trim();
|
|
3635
|
+
}
|
|
3636
|
+
|
|
3637
|
+
log('Question', index + 1, ':', {
|
|
3638
|
+
questionText: questionText.substring(0, 50) + '...',
|
|
3639
|
+
answerText: answerText.substring(0, 50) + '...',
|
|
3640
|
+
isCorrect: isCorrect
|
|
3641
|
+
});
|
|
3642
|
+
|
|
3212
3643
|
LRS.questionAnswered({
|
|
3213
|
-
questionId:
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3644
|
+
questionId: questionId,
|
|
3645
|
+
questionNumber: index + 1,
|
|
3646
|
+
questionText: questionText.substring(0, 500),
|
|
3647
|
+
answer: answerText.substring(0, 500),
|
|
3648
|
+
correctAnswer: correctAnswerText.substring(0, 500),
|
|
3649
|
+
result: isCorrect ? 'correct' : 'incorrect',
|
|
3650
|
+
assessmentName: assessmentName,
|
|
3651
|
+
lessonName: lessonInfo.name,
|
|
3652
|
+
sectionName: lessonInfo.sectionName
|
|
3217
3653
|
});
|
|
3218
3654
|
});
|
|
3219
3655
|
}
|
|
3220
3656
|
|
|
3221
3657
|
LRS.assessmentEnded({
|
|
3222
3658
|
assessmentId: extractAssessmentId(node),
|
|
3659
|
+
assessmentName: assessmentName,
|
|
3223
3660
|
score: score,
|
|
3224
|
-
questionCount: questions.length
|
|
3661
|
+
questionCount: questions.length,
|
|
3662
|
+
lessonName: lessonInfo.name,
|
|
3663
|
+
sectionName: lessonInfo.sectionName
|
|
3225
3664
|
});
|
|
3226
3665
|
}
|
|
3227
3666
|
|
|
@@ -3345,7 +3784,7 @@ function generateLrsBridgeCode(options) {
|
|
|
3345
3784
|
// ========================================================================
|
|
3346
3785
|
|
|
3347
3786
|
function init() {
|
|
3348
|
-
log('Initializing LRS bridge v2.
|
|
3787
|
+
log('Initializing LRS bridge v2.5.0...');
|
|
3349
3788
|
|
|
3350
3789
|
// Extract course info early
|
|
3351
3790
|
extractCourseInfo();
|