YuJa SmartSpeaker
| function checkIfIsInQuiz() { | ||
| const href = window.location.href; | ||
| return ( | ||
| href?.includes('.quiz-lti-iad-prod.') && | ||
| href?.includes('/taking/') && | ||
| GLOBALS?.lmsType == LMS_TYPES.CANVAS | ||
| ); | ||
| } | ||
| async function startInjectingIntoQuiz() { | ||
| await detectLoginStatus(); | ||
| if (!checkIfIsInQuiz()) return; | ||
| const credentials = await getCredentials(); | ||
| try { | ||
| visualizerAction(credentials, LMS_TYPES.CANVAS_NEW_QUIZ); | ||
| } catch (e) { | ||
| console.error(e); | ||
| } | ||
| } | ||
| startInjectingIntoQuiz(); |
| /** | ||
| * injectScript - Inject internal script to available access to the `window` | ||
| * | ||
| * @param {type} file_path Local path of the internal script. | ||
| * @param {type} tag The tag as string, where the script will be append (default: 'body'). | ||
| * @see {@link http://stackoverflow.com/questions/20499994/access-window-variable-from-content-script} | ||
| * @see {@link https://gist.github.com/devjin0617/3e8d72d94c1b9e69690717a219644c7a} | ||
| */ | ||
| function injectScript(file_path, tag) { | ||
| var node = document.getElementsByTagName(tag)[0]; | ||
| var script = document.createElement('script'); | ||
| script.setAttribute('type', 'text/javascript'); | ||
| script.setAttribute('src', file_path); | ||
| node.appendChild(script); | ||
| } | ||
| function addSmartSpeakerStarter() { | ||
| injectScript(chrome.runtime.getURL('smart-speaker-starter.js'), 'body'); | ||
| } | ||
| function detectLMSType() { | ||
| if (GLOBALS.lmsType) { | ||
| return GLOBALS.lmsType; | ||
| } | ||
| if ( | ||
| document.getElementById('d2l_body') || | ||
| document.getElementsByClassName('d2l-body').length > 0 || | ||
| document.getElementsByClassName('content-panel').length > 0 | ||
| ) { | ||
| return LMS_TYPES.D2L; | ||
| } else if (window.location.href.includes('blackboard')) { | ||
| return LMS_TYPES.BLACKBOARD; | ||
| } | ||
| return null; | ||
| } | ||
| async function detectLoginStatus() { | ||
| const result = await getObjectFromGoogleStorage([ | ||
| 'smart-speaker-token', | ||
| 'smart-speaker-institute-domain', | ||
| 'smart-speaker-lms-type', | ||
| 'smart-speaker-institution-name', | ||
| 'smart-speaker-user-role', | ||
| 'smart-speaker-user-id', | ||
| 'smartSpeakerServerUrl', | ||
| 'smartSpeakerCDNUrl', | ||
| 'smartSpeakerPortalServerUrl', | ||
| ]); | ||
| if (result['smart-speaker-lms-type']) { | ||
| GLOBALS.lmsType = result['smart-speaker-lms-type']; | ||
| } | ||
| if (result['smart-speaker-institute-domain']) { | ||
| GLOBALS.domain = result['smart-speaker-institute-domain']; | ||
| } | ||
| if (result['smart-speaker-institution-name']) { | ||
| GLOBALS.institution = result['smart-speaker-institution-name']; | ||
| } | ||
| if (result['smart-speaker-user-role']) { | ||
| GLOBALS.userRole = result['smart-speaker-user-role']; | ||
| } | ||
| if (result['smart-speaker-user-id']) { | ||
| GLOBALS.userId = result['smart-speaker-user-id']; | ||
| } | ||
| if (result['smartSpeakerServerUrl']) { | ||
| GLOBALS.smartSpeakerServerUrl = result['smartSpeakerServerUrl']; | ||
| } | ||
| if (result['smartSpeakerCDNUrl']) { | ||
| GLOBALS.smartSpeakerCDNUrl = result['smartSpeakerCDNUrl']; | ||
| } | ||
| if (result['smartSpeakerPortalServerUrl']) { | ||
| GLOBALS.smartSpeakerPortalServerUrl = | ||
| result['smartSpeakerPortalServerUrl']; | ||
| } | ||
| if (result['smart-speaker-token']) { | ||
| GLOBALS.token = result['smart-speaker-token']; | ||
| isLoggedIn = true; | ||
| } else { | ||
| isLoggedIn = false; | ||
| } | ||
| } | ||
| function isSmartSpeakerActive() { | ||
| let isParentHostSameAsDomain = false; | ||
| if (window.location.ancestorOrigins?.length > 0) { | ||
| const parentWindow = new URL(window.location.ancestorOrigins[0]); | ||
| isParentHostSameAsDomain = parentWindow.hostname === GLOBALS.domain; | ||
| } | ||
| return ( | ||
| window.location.host === GLOBALS.domain || | ||
| validateEventOrigin(window.location) || | ||
| isParentHostSameAsDomain | ||
| ); | ||
| } | ||
| async function openPortal() { | ||
| if (GLOBALS.smartSpeakerPortalServerUrl) | ||
| window.open( | ||
| `${GLOBALS.smartSpeakerPortalServerUrl}/login/${GLOBALS.token}`, | ||
| '_blank', | ||
| ); | ||
| } | ||
| async function fetchingFunction(extension) { | ||
| const fetchingUrl = | ||
| `${GLOBALS.smartSpeakerServerUrl}/${extension}/` + GLOBALS.token; | ||
| const response = await fetch(fetchingUrl, {cache: 'no-store'}); | ||
| const enabledSetting = await response.text(); | ||
| return enabledSetting; | ||
| } | ||
| function listenForSmartSpeakerRemoved() { | ||
| const parentElement = document.body; | ||
| const callback = (mutationsList, observer) => { | ||
| for (const mutation of mutationsList) { | ||
| if (mutation.type === 'childList') { | ||
| for (const removedNode of mutation.removedNodes) { | ||
| if ( | ||
| removedNode.id === | ||
| 'panoramaSmartSpeakerInjectionContainer' | ||
| ) { | ||
| destroySmartSpeaker(); | ||
| observer.disconnect(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| const observer = new MutationObserver(callback); | ||
| // Set up the observer to observe child node changes in the parent element | ||
| observer.observe(parentElement, {childList: true, subtree: true}); | ||
| } | ||
| function addTabListener() { | ||
| chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { | ||
| if (msg && msg.source === 'popover.js') { | ||
| if (msg.data === 'loginStatus') { | ||
| if (isLoggedIn) { | ||
| let cleanedLMSType = cleanLmsType(GLOBALS.lmsType); | ||
| const fetchExtraSettingsFunction = async function () { | ||
| const enabledSmartSpeaker = await fetchingFunction( | ||
| 'institutionSmartSpeakerSettings', | ||
| ); | ||
| return {enabledSmartSpeaker}; | ||
| }; | ||
| fetchExtraSettingsFunction().then( | ||
| ({enabledSmartSpeaker}) => { | ||
| sendResponse({ | ||
| source: 'inject.js', | ||
| data: 'loggedIn', | ||
| domain: GLOBALS.domain, | ||
| lmsType: cleanedLMSType, | ||
| institution: GLOBALS.institution, | ||
| userRole: GLOBALS.userRole, | ||
| token: GLOBALS.token, | ||
| userId: GLOBALS.userId, | ||
| isSmartSpeakerActive: isSmartSpeakerActive(), | ||
| smartSpeakerPortalServerUrl: | ||
| GLOBALS.smartSpeakerPortalServerUrl, | ||
| smartSpeakerServerUrl: | ||
| GLOBALS.smartSpeakerServerUrl, | ||
| enabledSmartSpeaker: | ||
| enabledSmartSpeaker == 'true', | ||
| externalSmartSpeakerLaunched: | ||
| window?.smartSpeakerLoaded | ||
| ? window.smartSpeakerLoaded | ||
| : false, | ||
| }); | ||
| }, | ||
| ); | ||
| return true; | ||
| } else { | ||
| sendResponse({ | ||
| source: 'inject.js', | ||
| data: 'loggedOut', | ||
| }); | ||
| } | ||
| } else if (msg.data === 'logout') { | ||
| if (isLoggedIn) { | ||
| logoutFromChromeExtension(); | ||
| } | ||
| sendResponse({ | ||
| source: 'inject.js', | ||
| data: 'loggedOut', | ||
| }); | ||
| } else if (msg.data === 'openPortal') { | ||
| if (isLoggedIn) { | ||
| openPortal(); | ||
| } | ||
| } else if (msg.data === 'launchSmartSpeaker') { | ||
| if (isLoggedIn) { | ||
| injectSmartSpeakerScript(); | ||
| listenForSmartSpeakerRemoved(); | ||
| } | ||
| } else if (msg.data === 'destroySmartSpeaker') { | ||
| destroySmartSpeaker(); | ||
| } | ||
| } | ||
| // it might be sending the message to the next listener, but that is ok | ||
| }); | ||
| } | ||
| async function logoutFromChromeExtension() { | ||
| await removeObjectToGoogleStorage([ | ||
| 'smart-speaker-token', | ||
| 'smart-speaker-key', | ||
| 'smart-speaker-institute-domain', | ||
| 'smart-speaker-lms-type', | ||
| 'smart-speaker-institution-name', | ||
| 'smart-speaker-user-role', | ||
| 'smart-speaker-user-id', | ||
| 'smartSpeakerServerUrl', | ||
| 'smartSpeakerCDNUrl', | ||
| 'smartSpeakerPortalServerUrl', | ||
| ]); | ||
| isLoggedIn = false; | ||
| } |
@@ -34,2 +34,52 @@ const ULTRA_HOME_TAB_ELEMENT_MAP = { | ||
| if (window.location.pathname.endsWith('/admin')) { | ||
| // Also listen for popstate events (browser back/forward) | ||
| window.addEventListener('popstate', function () { | ||
| if (window.location.pathname.endsWith('/admin')) { | ||
| setTimeout(() => { | ||
| injectOnDOMLoaderForNewBB(); | ||
| }, 100); | ||
| } | ||
| }); | ||
| const adminUrlChangeObserver = new MutationObserver(() => { | ||
| if (!window.location.pathname.endsWith('/admin')) { | ||
| adminUrlChangeObserver.disconnect(); | ||
| setTimeout(() => { | ||
| injectOnDOMLoaderForNewBB(); | ||
| }, 100); | ||
| } | ||
| }); | ||
| adminUrlChangeObserver.observe(document.body, { | ||
| childList: true, | ||
| subtree: true, | ||
| attributes: true, | ||
| }); | ||
| const checkUrlChange = () => { | ||
| if (!window.location.pathname.endsWith('/admin')) { | ||
| window.removeEventListener('hashchange', checkUrlChange); | ||
| setTimeout(() => { | ||
| injectOnDOMLoaderForNewBB(); | ||
| }, 100); | ||
| } | ||
| }; | ||
| window.addEventListener('hashchange', checkUrlChange); | ||
| const originalPushState = history.pushState; | ||
| const originalReplaceState = history.replaceState; | ||
| history.pushState = function (...args) { | ||
| originalPushState.apply(history, args); | ||
| setTimeout(checkUrlChange, 50); | ||
| }; | ||
| history.replaceState = function (...args) { | ||
| originalReplaceState.apply(history, args); | ||
| setTimeout(checkUrlChange, 50); | ||
| }; | ||
| } | ||
| let currHomePageTab = detectBlackboardHomeTab(); | ||
@@ -36,0 +86,0 @@ injectVisualizerOnHomePageUltraBB(currHomePageTab); |
+1
-0
@@ -25,2 +25,3 @@ let receivedMessage = false; | ||
| SMART_SPEAKER: 'smart-speaker', | ||
| CANVAS_NEW_QUIZ: 'canvas-new-quiz', | ||
| }; | ||
@@ -27,0 +28,0 @@ |
+5
-2
@@ -5,3 +5,3 @@ { | ||
| "author": "YuJa SmartSpeaker", | ||
| "version": "5.0.0", | ||
| "version": "5.1.0", | ||
| "description": "Enables YuJa SmartSpeaker visualizations for users.", | ||
@@ -31,3 +31,6 @@ "content_scripts": [ | ||
| "globals.js", | ||
| "smart-speaker-starter.js" | ||
| "smart-speaker-starter.js", | ||
| "inject-helpers.js", | ||
| "inject-canvas-new-quiz.js", | ||
| "visualizer-injector.js" | ||
| ] | ||
@@ -34,0 +37,0 @@ } |
+30
-15
@@ -117,2 +117,3 @@ <!DOCTYPE html> | ||
| #launchPortalContainer, | ||
| #launchWebsiteAccessibilityContainer, | ||
| #copyTokenContainer { | ||
@@ -147,3 +148,15 @@ border: 2px solid #442b6f; | ||
| } | ||
| #launchPortalContainer:focus-visible, | ||
| #copyTokenContainer:focus-visible, | ||
| #launchWebsiteAccessibilityContainer:focus-visible { | ||
| background-color: #442b6f; | ||
| cursor: pointer; | ||
| } | ||
| #launchPortalContainer:focus-visible #launchPortalBtn, | ||
| #copyTokenContainer:focus-visible #copyTokenBtn, | ||
| #launchWebsiteAccessibilityContainer:focus-visible | ||
| #launchWebsiteAccessibilityButton { | ||
| color: #ffffff; | ||
| } | ||
| #logoutButtonContainer { | ||
@@ -174,2 +187,6 @@ border: 4px solid #c93e3e; | ||
| .hidden { | ||
| display: none !important; | ||
| } | ||
| #copyTokenSuccessContainer { | ||
@@ -184,6 +201,2 @@ opacity: 0; | ||
| .hidden { | ||
| display: none !important; | ||
| } | ||
| .pano-extension-icon { | ||
@@ -439,3 +452,7 @@ background-size: 100% !important; | ||
| <span class="yuja-logo"> | ||
| <img src="./smart-speaker-main-icon.svg" width="30px" height="30px" /> | ||
| <img | ||
| src="./smart-speaker-main-icon.svg" | ||
| width="30px" | ||
| height="30px" | ||
| /> | ||
| </span> | ||
@@ -445,5 +462,3 @@ </div> | ||
| <div class="plugin-title"> | ||
| <div role="heading" aria-level="1"> | ||
| YuJa SmartSpeaker | ||
| </div> | ||
| <div role="heading" aria-level="1">YuJa SmartSpeaker</div> | ||
| <span | ||
@@ -566,4 +581,5 @@ id="loginDetailsBtn" | ||
| style="position: relative" | ||
| role="button" | ||
| > | ||
| <div id="copyTokenBtn" role="button"> | ||
| <div id="copyTokenBtn"> | ||
| <span id="copyTokenText" | ||
@@ -604,4 +620,2 @@ >Copy Authentication Token</span | ||
| <div id="loginDetails" style="min-height: 100px"> | ||
| <div | ||
@@ -648,5 +662,5 @@ id="SmartSpeakerListTile" | ||
| <div id="launchDescriptionText" class="sub-text"> | ||
| The SmartSpeaker Platform offers accessibility tools, | ||
| advanced analytics and reporting for all learning | ||
| environments. | ||
| The SmartSpeaker Platform offers accessibility | ||
| tools, advanced analytics and reporting for all | ||
| learning environments. | ||
| </div> | ||
@@ -658,4 +672,5 @@ </div> | ||
| tabindex="0" | ||
| role="button" | ||
| > | ||
| <div id="launchPortalBtn" role="button">Launch</div> | ||
| <div id="launchPortalBtn">Launch</div> | ||
| </div> | ||
@@ -662,0 +677,0 @@ </div> |
@@ -5,2 +5,3 @@ const INJECT_VISUALIZER_LMS_MAP = [ | ||
| LMS_TYPES.SMART_SPEAKER, | ||
| LMS_TYPES.CANVAS_NEW_QUIZ, | ||
| ]; | ||
@@ -7,0 +8,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet