@patch-adams/core 1.4.0 → 1.4.3
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 +283 -54
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +283 -54
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +283 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +283 -54
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1565,24 +1565,12 @@ function generateLrsBridgeCode(options) {
|
|
|
1565
1565
|
// Store for document API data
|
|
1566
1566
|
var documentApiData = null;
|
|
1567
1567
|
var documentApiFetched = false;
|
|
1568
|
+
var sharedLinkApiData = null;
|
|
1568
1569
|
|
|
1569
1570
|
/**
|
|
1570
|
-
*
|
|
1571
|
-
* Endpoint: /api/v3/documents/{documentId}
|
|
1571
|
+
* Detect Bravais core URL from current page context
|
|
1572
1572
|
*/
|
|
1573
|
-
function
|
|
1574
|
-
if (!documentId) {
|
|
1575
|
-
callback(null);
|
|
1576
|
-
return;
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
if (documentApiFetched) {
|
|
1580
|
-
callback(documentApiData);
|
|
1581
|
-
return;
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
// Detect Bravais core URL
|
|
1585
|
-
var coreUrl = null;
|
|
1573
|
+
function detectCoreUrl() {
|
|
1586
1574
|
var urlsToCheck = [window.location.href, document.referrer];
|
|
1587
1575
|
|
|
1588
1576
|
try {
|
|
@@ -1598,13 +1586,91 @@ function generateLrsBridgeCode(options) {
|
|
|
1598
1586
|
} catch (e) {}
|
|
1599
1587
|
|
|
1600
1588
|
for (var j = 0; j < urlsToCheck.length; j++) {
|
|
1601
|
-
var match = urlsToCheck[j].match(/(https
|
|
1589
|
+
var match = urlsToCheck[j].match(/(https?://core-[a-zA-Z0-9_-]+.bravais.com)/);
|
|
1602
1590
|
if (match) {
|
|
1603
|
-
|
|
1604
|
-
break;
|
|
1591
|
+
return match[1];
|
|
1605
1592
|
}
|
|
1606
1593
|
}
|
|
1594
|
+
return null;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
/**
|
|
1598
|
+
* Fetch shared link data from Bravais API to get the real document ID
|
|
1599
|
+
* Endpoint: /api/v3/sharedLinks/token/{token}
|
|
1600
|
+
* Returns: { documentId, documentName, format, ... }
|
|
1601
|
+
*/
|
|
1602
|
+
function fetchSharedLinkData(token, callback) {
|
|
1603
|
+
if (!token) {
|
|
1604
|
+
callback(null);
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
1607
|
|
|
1608
|
+
var coreUrl = detectCoreUrl();
|
|
1609
|
+
if (!coreUrl) {
|
|
1610
|
+
log('No Bravais core URL detected for shared link fetch');
|
|
1611
|
+
callback(null);
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
var apiUrl = coreUrl + '/api/v3/sharedLinks/token/' + token;
|
|
1616
|
+
log('Fetching shared link data from:', apiUrl);
|
|
1617
|
+
|
|
1618
|
+
var xhr = new XMLHttpRequest();
|
|
1619
|
+
xhr.open('GET', apiUrl, true);
|
|
1620
|
+
xhr.withCredentials = true;
|
|
1621
|
+
xhr.setRequestHeader('Accept', 'application/json');
|
|
1622
|
+
|
|
1623
|
+
xhr.onreadystatechange = function() {
|
|
1624
|
+
if (xhr.readyState === 4) {
|
|
1625
|
+
log('Shared link API response status:', xhr.status);
|
|
1626
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
1627
|
+
try {
|
|
1628
|
+
sharedLinkApiData = JSON.parse(xhr.responseText);
|
|
1629
|
+
log('Shared link data received:', sharedLinkApiData);
|
|
1630
|
+
if (sharedLinkApiData.documentId) {
|
|
1631
|
+
log('Real document ID from shared link:', sharedLinkApiData.documentId);
|
|
1632
|
+
}
|
|
1633
|
+
callback(sharedLinkApiData);
|
|
1634
|
+
} catch (e) {
|
|
1635
|
+
warn('Error parsing shared link data:', e);
|
|
1636
|
+
callback(null);
|
|
1637
|
+
}
|
|
1638
|
+
} else {
|
|
1639
|
+
warn('Shared link fetch failed. Status:', xhr.status);
|
|
1640
|
+
callback(null);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
};
|
|
1644
|
+
|
|
1645
|
+
xhr.onerror = function() {
|
|
1646
|
+
log('Network error fetching shared link data');
|
|
1647
|
+
callback(null);
|
|
1648
|
+
};
|
|
1649
|
+
|
|
1650
|
+
try {
|
|
1651
|
+
xhr.send();
|
|
1652
|
+
} catch (e) {
|
|
1653
|
+
warn('Error sending shared link request:', e);
|
|
1654
|
+
callback(null);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
/**
|
|
1659
|
+
* Fetch document metadata from Bravais API to get GUIDs
|
|
1660
|
+
* Endpoint: /api/v3/documents/{documentId}
|
|
1661
|
+
*/
|
|
1662
|
+
function fetchDocumentMetadata(documentId, callback) {
|
|
1663
|
+
if (!documentId) {
|
|
1664
|
+
callback(null);
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
if (documentApiFetched) {
|
|
1669
|
+
callback(documentApiData);
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
var coreUrl = detectCoreUrl();
|
|
1608
1674
|
if (!coreUrl) {
|
|
1609
1675
|
log('No Bravais core URL detected for document metadata fetch');
|
|
1610
1676
|
documentApiFetched = true;
|
|
@@ -1628,13 +1694,24 @@ function generateLrsBridgeCode(options) {
|
|
|
1628
1694
|
try {
|
|
1629
1695
|
documentApiData = JSON.parse(xhr.responseText);
|
|
1630
1696
|
log('Document metadata received:', documentApiData);
|
|
1697
|
+
// Validate that we got the required fields
|
|
1698
|
+
if (documentApiData.guid) {
|
|
1699
|
+
log('Document GUID found:', documentApiData.guid);
|
|
1700
|
+
} else {
|
|
1701
|
+
warn('Document API response missing guid field!', documentApiData);
|
|
1702
|
+
}
|
|
1703
|
+
if (documentApiData.latestVersion && documentApiData.latestVersion.guid) {
|
|
1704
|
+
log('Version GUID found:', documentApiData.latestVersion.guid);
|
|
1705
|
+
} else {
|
|
1706
|
+
warn('Document API response missing latestVersion.guid field!', documentApiData);
|
|
1707
|
+
}
|
|
1631
1708
|
callback(documentApiData);
|
|
1632
1709
|
} catch (e) {
|
|
1633
1710
|
warn('Error parsing document metadata:', e);
|
|
1634
1711
|
callback(null);
|
|
1635
1712
|
}
|
|
1636
1713
|
} else {
|
|
1637
|
-
|
|
1714
|
+
warn('Document metadata fetch failed. Status:', xhr.status, 'Response:', xhr.responseText);
|
|
1638
1715
|
callback(null);
|
|
1639
1716
|
}
|
|
1640
1717
|
}
|
|
@@ -2018,6 +2095,14 @@ function generateLrsBridgeCode(options) {
|
|
|
2018
2095
|
function buildCourseActivityObject() {
|
|
2019
2096
|
if (!LRS.courseInfo) return null;
|
|
2020
2097
|
|
|
2098
|
+
// Warn if we don't have the required GUIDs for Bravais aggregation
|
|
2099
|
+
if (!LRS.courseInfo.guid) {
|
|
2100
|
+
warn('Missing document GUID - statement may fail Bravais aggregation. Using numeric ID:', LRS.courseInfo.documentId);
|
|
2101
|
+
}
|
|
2102
|
+
if (!LRS.courseInfo.versionGuid) {
|
|
2103
|
+
warn('Missing version GUID - statement may fail Bravais aggregation (document_version_id will be null)');
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2021
2106
|
var obj = {
|
|
2022
2107
|
objectType: 'Activity',
|
|
2023
2108
|
id: LRS.courseInfo.id,
|
|
@@ -2030,10 +2115,13 @@ function generateLrsBridgeCode(options) {
|
|
|
2030
2115
|
// Add Xyleme-format extensions
|
|
2031
2116
|
obj.definition.extensions = {};
|
|
2032
2117
|
|
|
2033
|
-
// Version ID in Xyleme IRI format
|
|
2118
|
+
// Version ID in Xyleme IRI format - REQUIRED for Bravais aggregation
|
|
2034
2119
|
if (LRS.courseInfo.versionGuid) {
|
|
2035
2120
|
obj.definition.extensions['versionId'] =
|
|
2036
2121
|
'http://xyleme.com/bravais/document.version/' + LRS.courseInfo.versionGuid;
|
|
2122
|
+
} else {
|
|
2123
|
+
// Log error - this will cause aggregation failure
|
|
2124
|
+
warn('versionId extension not set - this statement will fail Bravais aggregation!');
|
|
2037
2125
|
}
|
|
2038
2126
|
|
|
2039
2127
|
// Format (e.g., "SCORM by Rise")
|
|
@@ -2639,49 +2727,146 @@ function generateLrsBridgeCode(options) {
|
|
|
2639
2727
|
function setupMediaInterceptors() {
|
|
2640
2728
|
if (!TRACK_MEDIA) return;
|
|
2641
2729
|
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2730
|
+
// Track which elements we've already attached listeners to
|
|
2731
|
+
var trackedMedia = new WeakSet();
|
|
2732
|
+
|
|
2733
|
+
// Helper to get media name/title from element or surrounding context
|
|
2734
|
+
function getMediaName(el) {
|
|
2735
|
+
// Check element attributes
|
|
2736
|
+
var name = el.title || el.getAttribute('aria-label') || el.getAttribute('data-name');
|
|
2737
|
+
if (name) return name;
|
|
2738
|
+
|
|
2739
|
+
// Check parent Rise video block for title
|
|
2740
|
+
var videoBlock = findClosest(el, '[data-block-type="video"], .blocks-video, .video-block');
|
|
2741
|
+
if (videoBlock) {
|
|
2742
|
+
var titleEl = videoBlock.querySelector('.video-title, .block-title, [class*="title"]');
|
|
2743
|
+
if (titleEl) return titleEl.textContent.trim();
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
// Use src filename as fallback
|
|
2747
|
+
var src = el.src || el.currentSrc || '';
|
|
2748
|
+
if (src) {
|
|
2749
|
+
var filename = src.split('/').pop().split('?')[0];
|
|
2750
|
+
if (filename && filename.length < 100) return filename;
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
return el.tagName.toLowerCase();
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
// Attach media event listeners to a single element
|
|
2757
|
+
function attachMediaListeners(el) {
|
|
2758
|
+
if (trackedMedia.has(el)) return;
|
|
2759
|
+
trackedMedia.add(el);
|
|
2760
|
+
|
|
2761
|
+
var mediaType = el.tagName.toLowerCase();
|
|
2762
|
+
log('Attaching media listeners to:', mediaType, el.src || el.currentSrc || 'no-src');
|
|
2763
|
+
|
|
2764
|
+
el.addEventListener('play', function() {
|
|
2765
|
+
log('Media play event captured:', mediaType);
|
|
2645
2766
|
LRS.mediaPlayed({
|
|
2646
|
-
type:
|
|
2647
|
-
src:
|
|
2648
|
-
name:
|
|
2649
|
-
currentTime:
|
|
2650
|
-
duration:
|
|
2767
|
+
type: mediaType,
|
|
2768
|
+
src: el.src || el.currentSrc || '',
|
|
2769
|
+
name: getMediaName(el),
|
|
2770
|
+
currentTime: el.currentTime || 0,
|
|
2771
|
+
duration: el.duration || 0,
|
|
2651
2772
|
action: 'play'
|
|
2652
2773
|
});
|
|
2653
|
-
}
|
|
2654
|
-
}, true);
|
|
2774
|
+
});
|
|
2655
2775
|
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2776
|
+
el.addEventListener('pause', function() {
|
|
2777
|
+
// Ignore pause events that fire right before ended
|
|
2778
|
+
if (el.ended) return;
|
|
2779
|
+
log('Media pause event captured:', mediaType);
|
|
2659
2780
|
LRS.mediaPlayed({
|
|
2660
|
-
type:
|
|
2661
|
-
src:
|
|
2662
|
-
name:
|
|
2663
|
-
currentTime:
|
|
2664
|
-
duration:
|
|
2781
|
+
type: mediaType,
|
|
2782
|
+
src: el.src || el.currentSrc || '',
|
|
2783
|
+
name: getMediaName(el),
|
|
2784
|
+
currentTime: el.currentTime || 0,
|
|
2785
|
+
duration: el.duration || 0,
|
|
2665
2786
|
action: 'pause'
|
|
2666
2787
|
});
|
|
2667
|
-
}
|
|
2668
|
-
}, true);
|
|
2788
|
+
});
|
|
2669
2789
|
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
if (target.tagName === 'VIDEO' || target.tagName === 'AUDIO') {
|
|
2790
|
+
el.addEventListener('ended', function() {
|
|
2791
|
+
log('Media ended event captured:', mediaType);
|
|
2673
2792
|
LRS.mediaPlayed({
|
|
2674
|
-
type:
|
|
2675
|
-
src:
|
|
2676
|
-
name:
|
|
2677
|
-
currentTime:
|
|
2678
|
-
duration:
|
|
2793
|
+
type: mediaType,
|
|
2794
|
+
src: el.src || el.currentSrc || '',
|
|
2795
|
+
name: getMediaName(el),
|
|
2796
|
+
currentTime: el.duration || 0,
|
|
2797
|
+
duration: el.duration || 0,
|
|
2679
2798
|
action: 'completed'
|
|
2680
2799
|
});
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
// Scan for and attach listeners to all video/audio elements
|
|
2804
|
+
function scanForMedia(root) {
|
|
2805
|
+
var elements = (root || document).querySelectorAll('video, audio');
|
|
2806
|
+
log('Scanning for media elements, found:', elements.length);
|
|
2807
|
+
for (var i = 0; i < elements.length; i++) {
|
|
2808
|
+
attachMediaListeners(elements[i]);
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
// Initial scan
|
|
2813
|
+
scanForMedia();
|
|
2814
|
+
|
|
2815
|
+
// Document-level capture for any we might miss
|
|
2816
|
+
document.addEventListener('play', function(e) {
|
|
2817
|
+
var target = e.target;
|
|
2818
|
+
if (target.tagName === 'VIDEO' || target.tagName === 'AUDIO') {
|
|
2819
|
+
// Attach listeners if not already tracked
|
|
2820
|
+
if (!trackedMedia.has(target)) {
|
|
2821
|
+
log('Captured play from untracked media, attaching listeners');
|
|
2822
|
+
attachMediaListeners(target);
|
|
2823
|
+
}
|
|
2681
2824
|
}
|
|
2682
2825
|
}, true);
|
|
2683
2826
|
|
|
2684
|
-
|
|
2827
|
+
// MutationObserver to detect dynamically added video/audio elements
|
|
2828
|
+
var mediaObserver = new MutationObserver(function(mutations) {
|
|
2829
|
+
var needsScan = false;
|
|
2830
|
+
mutations.forEach(function(mutation) {
|
|
2831
|
+
mutation.addedNodes.forEach(function(node) {
|
|
2832
|
+
if (node.nodeType === 1) {
|
|
2833
|
+
if (node.tagName === 'VIDEO' || node.tagName === 'AUDIO') {
|
|
2834
|
+
attachMediaListeners(node);
|
|
2835
|
+
} else if (node.querySelectorAll) {
|
|
2836
|
+
// Check for nested video/audio
|
|
2837
|
+
var nested = node.querySelectorAll('video, audio');
|
|
2838
|
+
if (nested.length > 0) {
|
|
2839
|
+
needsScan = true;
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
});
|
|
2844
|
+
});
|
|
2845
|
+
if (needsScan) {
|
|
2846
|
+
scanForMedia();
|
|
2847
|
+
}
|
|
2848
|
+
});
|
|
2849
|
+
|
|
2850
|
+
// Start observing once DOM is ready
|
|
2851
|
+
function startMediaObserver() {
|
|
2852
|
+
if (document.body) {
|
|
2853
|
+
mediaObserver.observe(document.body, {
|
|
2854
|
+
childList: true,
|
|
2855
|
+
subtree: true
|
|
2856
|
+
});
|
|
2857
|
+
log('Media mutation observer started');
|
|
2858
|
+
} else {
|
|
2859
|
+
setTimeout(startMediaObserver, 100);
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
startMediaObserver();
|
|
2863
|
+
|
|
2864
|
+
// Periodic rescan for Rise lazy-loaded content
|
|
2865
|
+
setInterval(function() {
|
|
2866
|
+
scanForMedia();
|
|
2867
|
+
}, 3000);
|
|
2868
|
+
|
|
2869
|
+
log('Media interceptors set up (enhanced)');
|
|
2685
2870
|
}
|
|
2686
2871
|
|
|
2687
2872
|
function setupNavigationInterceptors() {
|
|
@@ -2922,7 +3107,7 @@ function generateLrsBridgeCode(options) {
|
|
|
2922
3107
|
// ========================================================================
|
|
2923
3108
|
|
|
2924
3109
|
function init() {
|
|
2925
|
-
log('Initializing LRS bridge v2.
|
|
3110
|
+
log('Initializing LRS bridge v2.3.0...');
|
|
2926
3111
|
|
|
2927
3112
|
// Extract course info early
|
|
2928
3113
|
extractCourseInfo();
|
|
@@ -2958,9 +3143,18 @@ function generateLrsBridgeCode(options) {
|
|
|
2958
3143
|
|
|
2959
3144
|
// Fetch document metadata from API to get GUIDs (async)
|
|
2960
3145
|
// Then send course launched event
|
|
3146
|
+
var sharedLinkToken = LRS.courseInfo ? LRS.courseInfo.sharedLinkToken : null;
|
|
2961
3147
|
var documentId = LRS.courseInfo ? LRS.courseInfo.documentId : null;
|
|
2962
3148
|
|
|
2963
3149
|
function sendLaunchEvents() {
|
|
3150
|
+
// Log the course info state before sending statements
|
|
3151
|
+
log('Sending launch events with course info:', {
|
|
3152
|
+
id: LRS.courseInfo ? LRS.courseInfo.id : null,
|
|
3153
|
+
guid: LRS.courseInfo ? LRS.courseInfo.guid : null,
|
|
3154
|
+
versionGuid: LRS.courseInfo ? LRS.courseInfo.versionGuid : null,
|
|
3155
|
+
documentId: LRS.courseInfo ? LRS.courseInfo.documentId : null
|
|
3156
|
+
});
|
|
3157
|
+
|
|
2964
3158
|
if (TRACK_NAVIGATION) {
|
|
2965
3159
|
LRS.courseLaunched();
|
|
2966
3160
|
LRS.contentOpened({
|
|
@@ -2971,17 +3165,52 @@ function generateLrsBridgeCode(options) {
|
|
|
2971
3165
|
}
|
|
2972
3166
|
}
|
|
2973
3167
|
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
fetchDocumentMetadata(documentId, function(docData) {
|
|
3168
|
+
function fetchDocDataAndSend(docId) {
|
|
3169
|
+
fetchDocumentMetadata(docId, function(docData) {
|
|
2977
3170
|
if (docData) {
|
|
2978
3171
|
updateCourseInfoFromApi(docData);
|
|
2979
3172
|
}
|
|
2980
3173
|
// Send launch events after API fetch (success or failure)
|
|
2981
3174
|
setTimeout(sendLaunchEvents, 100);
|
|
2982
3175
|
});
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
if (!LRS.courseInfo.guid) {
|
|
3179
|
+
// First, try to get real document ID from shared link token
|
|
3180
|
+
// The URL path contains a thin pack file ID, not the real document ID
|
|
3181
|
+
if (sharedLinkToken) {
|
|
3182
|
+
log('Fetching shared link data to get real document ID...');
|
|
3183
|
+
fetchSharedLinkData(sharedLinkToken, function(linkData) {
|
|
3184
|
+
if (linkData && linkData.documentId) {
|
|
3185
|
+
// Got the real document ID from shared link
|
|
3186
|
+
log('Got real document ID from shared link:', linkData.documentId);
|
|
3187
|
+
LRS.courseInfo.documentId = linkData.documentId;
|
|
3188
|
+
// Update shared link name if available
|
|
3189
|
+
if (linkData.documentName) {
|
|
3190
|
+
LRS.courseInfo.sharedLinkName = linkData.documentName;
|
|
3191
|
+
}
|
|
3192
|
+
fetchDocDataAndSend(linkData.documentId);
|
|
3193
|
+
} else if (documentId) {
|
|
3194
|
+
// Fallback to extracted document ID (may be thin pack ID)
|
|
3195
|
+
warn('Could not get document ID from shared link, using extracted ID:', documentId);
|
|
3196
|
+
fetchDocDataAndSend(documentId);
|
|
3197
|
+
} else {
|
|
3198
|
+
// No document ID available
|
|
3199
|
+
warn('No document ID available - statements may fail aggregation');
|
|
3200
|
+
setTimeout(sendLaunchEvents, 100);
|
|
3201
|
+
}
|
|
3202
|
+
});
|
|
3203
|
+
} else if (documentId) {
|
|
3204
|
+
// No shared link token, try with extracted document ID
|
|
3205
|
+
log('No shared link token, fetching document data with ID:', documentId);
|
|
3206
|
+
fetchDocDataAndSend(documentId);
|
|
3207
|
+
} else {
|
|
3208
|
+
// No identifiers available
|
|
3209
|
+
warn('No shared link token or document ID - statements may fail aggregation');
|
|
3210
|
+
setTimeout(sendLaunchEvents, 500);
|
|
3211
|
+
}
|
|
2983
3212
|
} else {
|
|
2984
|
-
// Already have GUID
|
|
3213
|
+
// Already have GUID - send after short delay
|
|
2985
3214
|
setTimeout(sendLaunchEvents, 500);
|
|
2986
3215
|
}
|
|
2987
3216
|
|