@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.cjs
CHANGED
|
@@ -1573,24 +1573,12 @@ function generateLrsBridgeCode(options) {
|
|
|
1573
1573
|
// Store for document API data
|
|
1574
1574
|
var documentApiData = null;
|
|
1575
1575
|
var documentApiFetched = false;
|
|
1576
|
+
var sharedLinkApiData = null;
|
|
1576
1577
|
|
|
1577
1578
|
/**
|
|
1578
|
-
*
|
|
1579
|
-
* Endpoint: /api/v3/documents/{documentId}
|
|
1579
|
+
* Detect Bravais core URL from current page context
|
|
1580
1580
|
*/
|
|
1581
|
-
function
|
|
1582
|
-
if (!documentId) {
|
|
1583
|
-
callback(null);
|
|
1584
|
-
return;
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
if (documentApiFetched) {
|
|
1588
|
-
callback(documentApiData);
|
|
1589
|
-
return;
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
// Detect Bravais core URL
|
|
1593
|
-
var coreUrl = null;
|
|
1581
|
+
function detectCoreUrl() {
|
|
1594
1582
|
var urlsToCheck = [window.location.href, document.referrer];
|
|
1595
1583
|
|
|
1596
1584
|
try {
|
|
@@ -1606,13 +1594,91 @@ function generateLrsBridgeCode(options) {
|
|
|
1606
1594
|
} catch (e) {}
|
|
1607
1595
|
|
|
1608
1596
|
for (var j = 0; j < urlsToCheck.length; j++) {
|
|
1609
|
-
var match = urlsToCheck[j].match(/(https
|
|
1597
|
+
var match = urlsToCheck[j].match(/(https?://core-[a-zA-Z0-9_-]+.bravais.com)/);
|
|
1610
1598
|
if (match) {
|
|
1611
|
-
|
|
1612
|
-
break;
|
|
1599
|
+
return match[1];
|
|
1613
1600
|
}
|
|
1614
1601
|
}
|
|
1602
|
+
return null;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
/**
|
|
1606
|
+
* Fetch shared link data from Bravais API to get the real document ID
|
|
1607
|
+
* Endpoint: /api/v3/sharedLinks/token/{token}
|
|
1608
|
+
* Returns: { documentId, documentName, format, ... }
|
|
1609
|
+
*/
|
|
1610
|
+
function fetchSharedLinkData(token, callback) {
|
|
1611
|
+
if (!token) {
|
|
1612
|
+
callback(null);
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
1615
|
|
|
1616
|
+
var coreUrl = detectCoreUrl();
|
|
1617
|
+
if (!coreUrl) {
|
|
1618
|
+
log('No Bravais core URL detected for shared link fetch');
|
|
1619
|
+
callback(null);
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
var apiUrl = coreUrl + '/api/v3/sharedLinks/token/' + token;
|
|
1624
|
+
log('Fetching shared link data from:', apiUrl);
|
|
1625
|
+
|
|
1626
|
+
var xhr = new XMLHttpRequest();
|
|
1627
|
+
xhr.open('GET', apiUrl, true);
|
|
1628
|
+
xhr.withCredentials = true;
|
|
1629
|
+
xhr.setRequestHeader('Accept', 'application/json');
|
|
1630
|
+
|
|
1631
|
+
xhr.onreadystatechange = function() {
|
|
1632
|
+
if (xhr.readyState === 4) {
|
|
1633
|
+
log('Shared link API response status:', xhr.status);
|
|
1634
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
1635
|
+
try {
|
|
1636
|
+
sharedLinkApiData = JSON.parse(xhr.responseText);
|
|
1637
|
+
log('Shared link data received:', sharedLinkApiData);
|
|
1638
|
+
if (sharedLinkApiData.documentId) {
|
|
1639
|
+
log('Real document ID from shared link:', sharedLinkApiData.documentId);
|
|
1640
|
+
}
|
|
1641
|
+
callback(sharedLinkApiData);
|
|
1642
|
+
} catch (e) {
|
|
1643
|
+
warn('Error parsing shared link data:', e);
|
|
1644
|
+
callback(null);
|
|
1645
|
+
}
|
|
1646
|
+
} else {
|
|
1647
|
+
warn('Shared link fetch failed. Status:', xhr.status);
|
|
1648
|
+
callback(null);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
};
|
|
1652
|
+
|
|
1653
|
+
xhr.onerror = function() {
|
|
1654
|
+
log('Network error fetching shared link data');
|
|
1655
|
+
callback(null);
|
|
1656
|
+
};
|
|
1657
|
+
|
|
1658
|
+
try {
|
|
1659
|
+
xhr.send();
|
|
1660
|
+
} catch (e) {
|
|
1661
|
+
warn('Error sending shared link request:', e);
|
|
1662
|
+
callback(null);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
/**
|
|
1667
|
+
* Fetch document metadata from Bravais API to get GUIDs
|
|
1668
|
+
* Endpoint: /api/v3/documents/{documentId}
|
|
1669
|
+
*/
|
|
1670
|
+
function fetchDocumentMetadata(documentId, callback) {
|
|
1671
|
+
if (!documentId) {
|
|
1672
|
+
callback(null);
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
if (documentApiFetched) {
|
|
1677
|
+
callback(documentApiData);
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
var coreUrl = detectCoreUrl();
|
|
1616
1682
|
if (!coreUrl) {
|
|
1617
1683
|
log('No Bravais core URL detected for document metadata fetch');
|
|
1618
1684
|
documentApiFetched = true;
|
|
@@ -1636,13 +1702,24 @@ function generateLrsBridgeCode(options) {
|
|
|
1636
1702
|
try {
|
|
1637
1703
|
documentApiData = JSON.parse(xhr.responseText);
|
|
1638
1704
|
log('Document metadata received:', documentApiData);
|
|
1705
|
+
// Validate that we got the required fields
|
|
1706
|
+
if (documentApiData.guid) {
|
|
1707
|
+
log('Document GUID found:', documentApiData.guid);
|
|
1708
|
+
} else {
|
|
1709
|
+
warn('Document API response missing guid field!', documentApiData);
|
|
1710
|
+
}
|
|
1711
|
+
if (documentApiData.latestVersion && documentApiData.latestVersion.guid) {
|
|
1712
|
+
log('Version GUID found:', documentApiData.latestVersion.guid);
|
|
1713
|
+
} else {
|
|
1714
|
+
warn('Document API response missing latestVersion.guid field!', documentApiData);
|
|
1715
|
+
}
|
|
1639
1716
|
callback(documentApiData);
|
|
1640
1717
|
} catch (e) {
|
|
1641
1718
|
warn('Error parsing document metadata:', e);
|
|
1642
1719
|
callback(null);
|
|
1643
1720
|
}
|
|
1644
1721
|
} else {
|
|
1645
|
-
|
|
1722
|
+
warn('Document metadata fetch failed. Status:', xhr.status, 'Response:', xhr.responseText);
|
|
1646
1723
|
callback(null);
|
|
1647
1724
|
}
|
|
1648
1725
|
}
|
|
@@ -2026,6 +2103,14 @@ function generateLrsBridgeCode(options) {
|
|
|
2026
2103
|
function buildCourseActivityObject() {
|
|
2027
2104
|
if (!LRS.courseInfo) return null;
|
|
2028
2105
|
|
|
2106
|
+
// Warn if we don't have the required GUIDs for Bravais aggregation
|
|
2107
|
+
if (!LRS.courseInfo.guid) {
|
|
2108
|
+
warn('Missing document GUID - statement may fail Bravais aggregation. Using numeric ID:', LRS.courseInfo.documentId);
|
|
2109
|
+
}
|
|
2110
|
+
if (!LRS.courseInfo.versionGuid) {
|
|
2111
|
+
warn('Missing version GUID - statement may fail Bravais aggregation (document_version_id will be null)');
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2029
2114
|
var obj = {
|
|
2030
2115
|
objectType: 'Activity',
|
|
2031
2116
|
id: LRS.courseInfo.id,
|
|
@@ -2038,10 +2123,13 @@ function generateLrsBridgeCode(options) {
|
|
|
2038
2123
|
// Add Xyleme-format extensions
|
|
2039
2124
|
obj.definition.extensions = {};
|
|
2040
2125
|
|
|
2041
|
-
// Version ID in Xyleme IRI format
|
|
2126
|
+
// Version ID in Xyleme IRI format - REQUIRED for Bravais aggregation
|
|
2042
2127
|
if (LRS.courseInfo.versionGuid) {
|
|
2043
2128
|
obj.definition.extensions['versionId'] =
|
|
2044
2129
|
'http://xyleme.com/bravais/document.version/' + LRS.courseInfo.versionGuid;
|
|
2130
|
+
} else {
|
|
2131
|
+
// Log error - this will cause aggregation failure
|
|
2132
|
+
warn('versionId extension not set - this statement will fail Bravais aggregation!');
|
|
2045
2133
|
}
|
|
2046
2134
|
|
|
2047
2135
|
// Format (e.g., "SCORM by Rise")
|
|
@@ -2647,49 +2735,146 @@ function generateLrsBridgeCode(options) {
|
|
|
2647
2735
|
function setupMediaInterceptors() {
|
|
2648
2736
|
if (!TRACK_MEDIA) return;
|
|
2649
2737
|
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2738
|
+
// Track which elements we've already attached listeners to
|
|
2739
|
+
var trackedMedia = new WeakSet();
|
|
2740
|
+
|
|
2741
|
+
// Helper to get media name/title from element or surrounding context
|
|
2742
|
+
function getMediaName(el) {
|
|
2743
|
+
// Check element attributes
|
|
2744
|
+
var name = el.title || el.getAttribute('aria-label') || el.getAttribute('data-name');
|
|
2745
|
+
if (name) return name;
|
|
2746
|
+
|
|
2747
|
+
// Check parent Rise video block for title
|
|
2748
|
+
var videoBlock = findClosest(el, '[data-block-type="video"], .blocks-video, .video-block');
|
|
2749
|
+
if (videoBlock) {
|
|
2750
|
+
var titleEl = videoBlock.querySelector('.video-title, .block-title, [class*="title"]');
|
|
2751
|
+
if (titleEl) return titleEl.textContent.trim();
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
// Use src filename as fallback
|
|
2755
|
+
var src = el.src || el.currentSrc || '';
|
|
2756
|
+
if (src) {
|
|
2757
|
+
var filename = src.split('/').pop().split('?')[0];
|
|
2758
|
+
if (filename && filename.length < 100) return filename;
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
return el.tagName.toLowerCase();
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
// Attach media event listeners to a single element
|
|
2765
|
+
function attachMediaListeners(el) {
|
|
2766
|
+
if (trackedMedia.has(el)) return;
|
|
2767
|
+
trackedMedia.add(el);
|
|
2768
|
+
|
|
2769
|
+
var mediaType = el.tagName.toLowerCase();
|
|
2770
|
+
log('Attaching media listeners to:', mediaType, el.src || el.currentSrc || 'no-src');
|
|
2771
|
+
|
|
2772
|
+
el.addEventListener('play', function() {
|
|
2773
|
+
log('Media play event captured:', mediaType);
|
|
2653
2774
|
LRS.mediaPlayed({
|
|
2654
|
-
type:
|
|
2655
|
-
src:
|
|
2656
|
-
name:
|
|
2657
|
-
currentTime:
|
|
2658
|
-
duration:
|
|
2775
|
+
type: mediaType,
|
|
2776
|
+
src: el.src || el.currentSrc || '',
|
|
2777
|
+
name: getMediaName(el),
|
|
2778
|
+
currentTime: el.currentTime || 0,
|
|
2779
|
+
duration: el.duration || 0,
|
|
2659
2780
|
action: 'play'
|
|
2660
2781
|
});
|
|
2661
|
-
}
|
|
2662
|
-
}, true);
|
|
2782
|
+
});
|
|
2663
2783
|
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2784
|
+
el.addEventListener('pause', function() {
|
|
2785
|
+
// Ignore pause events that fire right before ended
|
|
2786
|
+
if (el.ended) return;
|
|
2787
|
+
log('Media pause event captured:', mediaType);
|
|
2667
2788
|
LRS.mediaPlayed({
|
|
2668
|
-
type:
|
|
2669
|
-
src:
|
|
2670
|
-
name:
|
|
2671
|
-
currentTime:
|
|
2672
|
-
duration:
|
|
2789
|
+
type: mediaType,
|
|
2790
|
+
src: el.src || el.currentSrc || '',
|
|
2791
|
+
name: getMediaName(el),
|
|
2792
|
+
currentTime: el.currentTime || 0,
|
|
2793
|
+
duration: el.duration || 0,
|
|
2673
2794
|
action: 'pause'
|
|
2674
2795
|
});
|
|
2675
|
-
}
|
|
2676
|
-
}, true);
|
|
2796
|
+
});
|
|
2677
2797
|
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
if (target.tagName === 'VIDEO' || target.tagName === 'AUDIO') {
|
|
2798
|
+
el.addEventListener('ended', function() {
|
|
2799
|
+
log('Media ended event captured:', mediaType);
|
|
2681
2800
|
LRS.mediaPlayed({
|
|
2682
|
-
type:
|
|
2683
|
-
src:
|
|
2684
|
-
name:
|
|
2685
|
-
currentTime:
|
|
2686
|
-
duration:
|
|
2801
|
+
type: mediaType,
|
|
2802
|
+
src: el.src || el.currentSrc || '',
|
|
2803
|
+
name: getMediaName(el),
|
|
2804
|
+
currentTime: el.duration || 0,
|
|
2805
|
+
duration: el.duration || 0,
|
|
2687
2806
|
action: 'completed'
|
|
2688
2807
|
});
|
|
2808
|
+
});
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
// Scan for and attach listeners to all video/audio elements
|
|
2812
|
+
function scanForMedia(root) {
|
|
2813
|
+
var elements = (root || document).querySelectorAll('video, audio');
|
|
2814
|
+
log('Scanning for media elements, found:', elements.length);
|
|
2815
|
+
for (var i = 0; i < elements.length; i++) {
|
|
2816
|
+
attachMediaListeners(elements[i]);
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2820
|
+
// Initial scan
|
|
2821
|
+
scanForMedia();
|
|
2822
|
+
|
|
2823
|
+
// Document-level capture for any we might miss
|
|
2824
|
+
document.addEventListener('play', function(e) {
|
|
2825
|
+
var target = e.target;
|
|
2826
|
+
if (target.tagName === 'VIDEO' || target.tagName === 'AUDIO') {
|
|
2827
|
+
// Attach listeners if not already tracked
|
|
2828
|
+
if (!trackedMedia.has(target)) {
|
|
2829
|
+
log('Captured play from untracked media, attaching listeners');
|
|
2830
|
+
attachMediaListeners(target);
|
|
2831
|
+
}
|
|
2689
2832
|
}
|
|
2690
2833
|
}, true);
|
|
2691
2834
|
|
|
2692
|
-
|
|
2835
|
+
// MutationObserver to detect dynamically added video/audio elements
|
|
2836
|
+
var mediaObserver = new MutationObserver(function(mutations) {
|
|
2837
|
+
var needsScan = false;
|
|
2838
|
+
mutations.forEach(function(mutation) {
|
|
2839
|
+
mutation.addedNodes.forEach(function(node) {
|
|
2840
|
+
if (node.nodeType === 1) {
|
|
2841
|
+
if (node.tagName === 'VIDEO' || node.tagName === 'AUDIO') {
|
|
2842
|
+
attachMediaListeners(node);
|
|
2843
|
+
} else if (node.querySelectorAll) {
|
|
2844
|
+
// Check for nested video/audio
|
|
2845
|
+
var nested = node.querySelectorAll('video, audio');
|
|
2846
|
+
if (nested.length > 0) {
|
|
2847
|
+
needsScan = true;
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
});
|
|
2852
|
+
});
|
|
2853
|
+
if (needsScan) {
|
|
2854
|
+
scanForMedia();
|
|
2855
|
+
}
|
|
2856
|
+
});
|
|
2857
|
+
|
|
2858
|
+
// Start observing once DOM is ready
|
|
2859
|
+
function startMediaObserver() {
|
|
2860
|
+
if (document.body) {
|
|
2861
|
+
mediaObserver.observe(document.body, {
|
|
2862
|
+
childList: true,
|
|
2863
|
+
subtree: true
|
|
2864
|
+
});
|
|
2865
|
+
log('Media mutation observer started');
|
|
2866
|
+
} else {
|
|
2867
|
+
setTimeout(startMediaObserver, 100);
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
startMediaObserver();
|
|
2871
|
+
|
|
2872
|
+
// Periodic rescan for Rise lazy-loaded content
|
|
2873
|
+
setInterval(function() {
|
|
2874
|
+
scanForMedia();
|
|
2875
|
+
}, 3000);
|
|
2876
|
+
|
|
2877
|
+
log('Media interceptors set up (enhanced)');
|
|
2693
2878
|
}
|
|
2694
2879
|
|
|
2695
2880
|
function setupNavigationInterceptors() {
|
|
@@ -2930,7 +3115,7 @@ function generateLrsBridgeCode(options) {
|
|
|
2930
3115
|
// ========================================================================
|
|
2931
3116
|
|
|
2932
3117
|
function init() {
|
|
2933
|
-
log('Initializing LRS bridge v2.
|
|
3118
|
+
log('Initializing LRS bridge v2.3.0...');
|
|
2934
3119
|
|
|
2935
3120
|
// Extract course info early
|
|
2936
3121
|
extractCourseInfo();
|
|
@@ -2966,9 +3151,18 @@ function generateLrsBridgeCode(options) {
|
|
|
2966
3151
|
|
|
2967
3152
|
// Fetch document metadata from API to get GUIDs (async)
|
|
2968
3153
|
// Then send course launched event
|
|
3154
|
+
var sharedLinkToken = LRS.courseInfo ? LRS.courseInfo.sharedLinkToken : null;
|
|
2969
3155
|
var documentId = LRS.courseInfo ? LRS.courseInfo.documentId : null;
|
|
2970
3156
|
|
|
2971
3157
|
function sendLaunchEvents() {
|
|
3158
|
+
// Log the course info state before sending statements
|
|
3159
|
+
log('Sending launch events with course info:', {
|
|
3160
|
+
id: LRS.courseInfo ? LRS.courseInfo.id : null,
|
|
3161
|
+
guid: LRS.courseInfo ? LRS.courseInfo.guid : null,
|
|
3162
|
+
versionGuid: LRS.courseInfo ? LRS.courseInfo.versionGuid : null,
|
|
3163
|
+
documentId: LRS.courseInfo ? LRS.courseInfo.documentId : null
|
|
3164
|
+
});
|
|
3165
|
+
|
|
2972
3166
|
if (TRACK_NAVIGATION) {
|
|
2973
3167
|
LRS.courseLaunched();
|
|
2974
3168
|
LRS.contentOpened({
|
|
@@ -2979,17 +3173,52 @@ function generateLrsBridgeCode(options) {
|
|
|
2979
3173
|
}
|
|
2980
3174
|
}
|
|
2981
3175
|
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
fetchDocumentMetadata(documentId, function(docData) {
|
|
3176
|
+
function fetchDocDataAndSend(docId) {
|
|
3177
|
+
fetchDocumentMetadata(docId, function(docData) {
|
|
2985
3178
|
if (docData) {
|
|
2986
3179
|
updateCourseInfoFromApi(docData);
|
|
2987
3180
|
}
|
|
2988
3181
|
// Send launch events after API fetch (success or failure)
|
|
2989
3182
|
setTimeout(sendLaunchEvents, 100);
|
|
2990
3183
|
});
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3186
|
+
if (!LRS.courseInfo.guid) {
|
|
3187
|
+
// First, try to get real document ID from shared link token
|
|
3188
|
+
// The URL path contains a thin pack file ID, not the real document ID
|
|
3189
|
+
if (sharedLinkToken) {
|
|
3190
|
+
log('Fetching shared link data to get real document ID...');
|
|
3191
|
+
fetchSharedLinkData(sharedLinkToken, function(linkData) {
|
|
3192
|
+
if (linkData && linkData.documentId) {
|
|
3193
|
+
// Got the real document ID from shared link
|
|
3194
|
+
log('Got real document ID from shared link:', linkData.documentId);
|
|
3195
|
+
LRS.courseInfo.documentId = linkData.documentId;
|
|
3196
|
+
// Update shared link name if available
|
|
3197
|
+
if (linkData.documentName) {
|
|
3198
|
+
LRS.courseInfo.sharedLinkName = linkData.documentName;
|
|
3199
|
+
}
|
|
3200
|
+
fetchDocDataAndSend(linkData.documentId);
|
|
3201
|
+
} else if (documentId) {
|
|
3202
|
+
// Fallback to extracted document ID (may be thin pack ID)
|
|
3203
|
+
warn('Could not get document ID from shared link, using extracted ID:', documentId);
|
|
3204
|
+
fetchDocDataAndSend(documentId);
|
|
3205
|
+
} else {
|
|
3206
|
+
// No document ID available
|
|
3207
|
+
warn('No document ID available - statements may fail aggregation');
|
|
3208
|
+
setTimeout(sendLaunchEvents, 100);
|
|
3209
|
+
}
|
|
3210
|
+
});
|
|
3211
|
+
} else if (documentId) {
|
|
3212
|
+
// No shared link token, try with extracted document ID
|
|
3213
|
+
log('No shared link token, fetching document data with ID:', documentId);
|
|
3214
|
+
fetchDocDataAndSend(documentId);
|
|
3215
|
+
} else {
|
|
3216
|
+
// No identifiers available
|
|
3217
|
+
warn('No shared link token or document ID - statements may fail aggregation');
|
|
3218
|
+
setTimeout(sendLaunchEvents, 500);
|
|
3219
|
+
}
|
|
2991
3220
|
} else {
|
|
2992
|
-
// Already have GUID
|
|
3221
|
+
// Already have GUID - send after short delay
|
|
2993
3222
|
setTimeout(sendLaunchEvents, 500);
|
|
2994
3223
|
}
|
|
2995
3224
|
|