scorm

Simplify-scorm window.open(scormUrl)


I am trying to open my SCORM education in a new window but when i do, all the reporting stops working (The same goes if i do it in an iframe). When i just do location.href = scormURL; then it works fine. My scorm file is in a different domain then the LMS.

It seems like the SCORM api doesn't get initialized after doing loadFromJSON(response) on my scorm response. But this problem only occurs when i open in a new window or show the scorm education in an iframe.

I use SCORM2004.


Solution

  • My scorm file is in a different domain then the LMS.

    This is your problem.

    The only link the SCORM package has to your LMS (and therefore to your reporting) is the SCORM API. Modern browsers do not allow CORS (cross-origin resource sharing) by default because it opens you to XSS (cross-site scripting) attacks.

    The package may be able to find the API if one exists on the external domain, but it will not be able to communicate with the LMS directly.

    The way I worked around this was to add a wrapper .js file on the external domain that hosted the SCORM API and used Window.postMessage to pass calls back to the LMS.

    You then use window.open or your iframe to open a page on the external domain which has access to scorm_wrapper.js and its own iframe that hosts your package.


    scorm_wrapper.js

    Because the Window.opener method is not reliable and not fully-featured when the current window was opened from a different origin, we will use the domain directly.

    Since we control the LMS, we know the domain it is running on and since we just opened the package, we know it should be active. We can postMessage it to ask for a reply.

    function receiveMessage(data) { 
        // We will return to this later...
    };
    
    // Be ready for the reply from the LMS.
    window.addEventListener('message', receiveMessage(event));
    
    // Ping the domain your LMS is running on.
    window.parent.postMessage('ping', 'https://example.com');
    

    When our LMS receives the message, it should reply with a postMessage of its own to event.origin which is the domain our package is running on.

    Now when we get the reply from the LMS, we know we have access.

    var connected = false;
    
    function receiveMessage(data) {
        // ...
    
        // Do not do anything unless the message was from
        // a domain we trust.
        if (event.origin !== 'https://example.com') return;
    
        switch (event.data) {
            case 'pong':
                connected = true;
                break;
            // We will return to this later...
        }
    }
    

    Now any time the wrapper's API object gets called from the package, we can pass the message on to the LMS.

    function sendMessage(args) {
        if (!connected) return;
        window.parent.postMessage(args, 'https://example.com');
    }
    
    const apiWrapper = {
        Initialize: function (args) {
            sendMessage({ method: 'Initialize', data: args });
        },
        // More SCORM endpoints...
    };
    
    window.API = apiWrapper;
    window.API_1484_11 = apiWrapper;
    

    Add cases to receiveMessage as needed.

    function receiveMessage(data) {
        // ...
        switch (event.data) {
            case 'pong':
                connected = true;
                break;
            case 'initialize-acknowledge`:
                //...
                break;
        }
    }
    

    This approach isn't great, but it gets you around a cross-domain issue safely and without needing access to server configurations.