# @rusticisoftware/scormcloud-api-v2-client-javascript

JavaScript client for @rusticisoftware/scormcloud-api-v2-client-javascript
    REST API used for SCORM Cloud integrations.
This SDK is automatically generated by the [Swagger Codegen](https://github.com/swagger-api/swagger-codegen) project:

- API version: 2.0
- Package version: 4.0.0
- Build package: io.swagger.codegen.languages.JavascriptClientCodegen

## Installation
### npm
[@rusticisoftware/scormcloud-api-v2-client-javascript](https://www.npmjs.com/package/@rusticisoftware/scormcloud-api-v2-client-javascript)

```shell
npm install @rusticisoftware/scormcloud-api-v2-client-javascript --save
```

### GitHub
[RusticiSoftware/scormcloud-api-v2-client-javascript](https://github.com/RusticiSoftware/scormcloud-api-v2-client-javascript)

```shell
npm install RusticiSoftware/scormcloud-api-v2-client-javascript --save
```

### Local
To use the library locally without pulling from the npm registry, first install the dependencies by changing
into the directory containing `package.json` (and this README). Let's call this `JAVASCRIPT_CLIENT_DIR`. Then run:

```shell
npm install
```

Next, [link](https://docs.npmjs.com/cli/link) it globally in npm with the following, also from `JAVASCRIPT_CLIENT_DIR`:

```shell
npm link
```

Finally, switch to the directory you want to use your @rusticisoftware/scormcloud-api-v2-client-javascript from, and run:

```shell
npm link /path/to/<JAVASCRIPT_CLIENT_DIR>
```

You should now be able to `require('@rusticisoftware/scormcloud-api-v2-client-javascript')` in javascript files from the directory you ran the last
command above from.

### For browser

The library also works in the browser environment via npm and [browserify](http://browserify.org/). After following
the above steps and installing browserify with `npm install -g browserify`,
perform the following (assuming *main.js* is your entry file, that's to say your javascript file where you actually
use this library):

```shell
browserify main.js > bundle.js
```

Then include *bundle.js* in the HTML pages.

### Webpack Configuration

Using Webpack you may encounter the following error: "Module not found: Error:
Cannot resolve module", most certainly you should disable AMD loader. Add/merge
the following section to your webpack config:

```javascript
module: {
    rules: [
        {
            parser: {
                amd: false
            }
        }
    ]
}
```

## Tips and Tricks
Working with headers will require passing a response parameter of the callback function. This allows for grabbing the header directly from the response object:
```javascript
// Note: This code is specifically designed to not modify any existing data
const dispatchApi = new ScormCloud.DispatchApi();
dispatchApi.updateDispatches(new ScormCloud.UpdateDispatchSchema(), { "since": new Date().toISOString() }, function(err, data, response) {
    console.log(response.headers["x-total-count"]);
});
```

## Changelog:
### Release 2.0.X:
- The return type for endpoints which used to return a File have changed to instead return a Blob object. This change
allows both Node and the browser to download the files appropriately.

Check the [changelog](https://cloud.scorm.com/docs/v2/reference/changelog/) for further details of what has changed.

## Sample Code
There are additional dependencies required to get the sample code running.
```
npm install browser-or-node prompt-sync
```

```javascript
const ScormCloud = require('@rusticisoftware/scormcloud-api-v2-client-javascript');
const fs = require('fs');

let prompt;
var jsEnv = require("browser-or-node");
if (jsEnv.isBrowser) {
    prompt = window.prompt;
}
if (jsEnv.isNode) {
    prompt = require('prompt-sync')({sigint: true});
}


// ScormCloud API credentials
// Note: These are not the same credentials used to log in to ScormCloud
const APP_ID = "APP_ID";
const SECRET_KEY = "SECRET_KEY";

// Sample values for data
const COURSE_PATH = "/PATH/TO/COURSE/RunTimeAdvancedCalls_SCORM20043rdEdition.zip";
let COURSE_FILE;

const COURSE_ID = "JS_SAMPLE_COURSE";
const LEARNER_ID = "JS_SAMPLE_COURSE_LEARNER";
const REGISTRATION_ID = "JS_SAMPLE_COURSE_REGISTRATION";

// String used for output formatting
const OUTPUT_BORDER = "---------------------------------------------------------\n";


/**
 * This sample will consist of:
 * 1. Creating a course.
 * 2. Registering a learner for the course.
 * 3. Building a link for the learner to take the course.
 * 4. Getting the learner's progress after having taken the course.
 * 5. Viewing all courses and registrations.
 * 6. Deleting all of the data created via this sample.
 *
 * All input variables used in this sample are defined up above.
 */
function main() {
    // Configure HTTP basic authorization: APP_NORMAL
    const APP_NORMAL = ScormCloud.ApiClient.instance.authentications['APP_NORMAL'];
    APP_NORMAL.username = APP_ID;
    APP_NORMAL.password = SECRET_KEY;

    // Create a course
    createCourse(COURSE_ID, COURSE_FILE, function(courseDetails) {
        // Show details of the newly imported course
        console.log("Newly Imported Course Details: ");
        console.log(courseDetails);

        // Create a registration for the course
        createRegistration(COURSE_ID, LEARNER_ID, REGISTRATION_ID, function() {
            // Create the registration launch link
            buildLaunchLink(REGISTRATION_ID, function(launchLink) {
                // Show the launch link
                console.log(OUTPUT_BORDER);
                console.log(`Launck Link: ${launchLink}`);
                console.log("Navigate to the url above to take the course. " +
                    (jsEnv.isNode ? "Hit enter once complete." : "Click OK on the in-browser prompt once complete."));
                prompt();

                // Get the results for the registration
                getResultForRegistration(REGISTRATION_ID, function(registrationProgress) {
                    // Show details of the registration progress
                    console.log(OUTPUT_BORDER);
                    console.log("Registration Progress: ");
                    console.log(registrationProgress);

                    // Get information about all the courses in ScormCloud
                    getAllCourses(function(courseList) {
                        // Show details of the courses
                        console.log(OUTPUT_BORDER);
                        console.log("Course List: ");
                        courseList.forEach((course) => {
                            console.log(course);
                        });

                        // Get information about all the registrations in ScormCloud
                        getAllRegistrations(function(registrationList) {
                            // Show details of the registrations
                            console.log(OUTPUT_BORDER);
                            console.log("Registration List: ");
                            registrationList.forEach((registration) => {
                                console.log(registration);
                            });

                            // Delete all the data created by this sample
                            cleanUp(COURSE_ID, REGISTRATION_ID);
                        });
                    });
                });
            });
        });
    });
}

function logErrorAndCleanUp(error) {
    console.error(error)

    // Delete all the data created by this sample
    cleanUp(COURSE_ID, REGISTRATION_ID);
}

/**
 * Sets the default OAuth token passed with all calls to the API.
 *
 * If a token is created with limited scope (i.e. read:registration),
 * calls that require a different permission set will error. Either a
 * new token needs to be generated with the correct scope, or the
 * default access token can be reset to None. This would cause the
 * request to be made with basic auth credentials (appId/ secret key)
 * instead.
 *
 * Additionally, you could create a new configuration object and set
 * the token on that object instead of the default access token. This
 * configuration would then be passed into the Api object:
 *
 * apiClient = new ScormCloud.ApiClient();
 * tokenRequest = {
 *     permissions: { scopes: [ "write:course", "read:course" ] },
 *     expiry: new Date((new Date()).getTime() + 2 * 60 * 1000).toISOString()
 * };
 * appManagementApi.createToken(tokenRequest, function (error, data) {
 *     if (error) {
 *         return logErrorAndCleanUp(error.response.text);
 *     }
 *     apiClient.authentications['OAUTH'].accessToken = data.result;
 *     courseApi = new ScormCloud.CourseApi(apiClient);
 *
 *     // Logic would go here
 * });
 *
 * Any calls that would use this CourseApi instance would then have the
 * write:course and read:course permissions passed automatically, but
 * other instances would be unaffected and continue to use other means
 * of authorization.
 *
 * @param {Array<String>} scopes List of permissions for calls made with the token.
 */
function configureOAuth(scopes, callback) {
    const appManagementApi = new ScormCloud.ApplicationManagementApi();

    // Set permissions and expiry time of the token
    // The expiry expected for token request must be in ISO-8601 format
    const expiry = new Date((new Date()).getTime() + 2 * 60 * 1000).toISOString();
    const permissions = { scopes: scopes };

    // Make the request to get the OAuth token
    tokenRequest = { permissions: permissions, expiry: expiry };
    appManagementApi.createToken(tokenRequest, function (error, data) {
        if (error) {
            return logErrorAndCleanUp(error.response.text);
        }

        // Set the default access token used with further API requests.
        // To remove the token, reset
        // ScormCloud.ApiClient.instance.authentications['OAUTH'].accessToken
        // back to null before the next call.
        const OAUTH = ScormCloud.ApiClient.instance.authentications['OAUTH'];
        OAUTH.accessToken = data.result;

        callback();
    });
}

/**
 * Creates a course by uploading the course from your local machine.
 * Courses are a package of content for a learner to consume.
 *
 * Other methods for importing a course exist. Check the documentation
 * for additional ways of importing a course.
 *
 * @param {String} courseId Id that will be used to identify the course.
 * @param {File} courseFile The File object containing the course contents.
 * @returns {CourseSchema} Detailed information about the newly uploaded course.
 */
function createCourse(courseId, courseFile, callback) {
    function createCourseLogic() {
        const courseApi = new ScormCloud.CourseApi();
        let jobId;

        // This call will use OAuth with the "write:course" scope
        // if configured.  Otherwise the basic auth credentials will be used
        courseApi.createUploadAndImportCourseJob(courseId, { file: courseFile }, function(error, data) {
            if (error) {
                return logErrorAndCleanUp(error.response.text);
            }

            jobId = data.result;

            function getUploadStatus(jobId) {
                // This call will use OAuth with the "read:course" scope
                // if configured.  Otherwise the basic auth credentials will be used
                let interval = setInterval(function () {
                    courseApi.getImportJobStatus(jobId, function(error, data) {
                        if (error) {
                            clearInterval(interval);
                            return logErrorAndCleanUp(error.response.text);
                        }

                        if (data.status == ScormCloud.ImportJobResultSchema.StatusEnum.RUNNING) {
                            return;
                        }

                        // If importing has finished (failure or success)
                        clearInterval(interval);

                        if (data.status == ScormCloud.ImportJobResultSchema.StatusEnum.ERROR) {
                            return logErrorAndCleanUp("Course is not properly formatted: " + data.message);
                        }

                        callback(data.importResult.course);
                    });
                }, 1000);
            }
            getUploadStatus(jobId);
        });
    }

    // (Optional) Further authenticate via OAuth token access
    // First line is with OAuth, second is without
    // configureOAuth([ "write:course", "read:course" ], createCourseLogic);
    createCourseLogic();
}

/**
 * Creates a registration allowing the learner to consume the course
 * content. A registration is the link between a learner and a single
 * course.
 *
 * @param {String} courseId Id of the course to register the learner for.
 * @param {String} learnerId Id that will be used to identify the learner.
 * @param {string} registrationId Id that will be used to identify the registration.
 */
function createRegistration(courseId, learnerId, registrationId, callback) {
    function createRegistrationLogic() {
        const registrationApi = new ScormCloud.RegistrationApi();
        const learner = { id: learnerId };
        const registration = { courseId: courseId, learner: learner, registrationId: registrationId };
        registrationApi.createRegistration(registration, {}, function(error) {
            if (error) {
                return logErrorAndCleanUp(error.response.text);
            }

            callback();
        });
    }

    // (Optional) Further authenticate via OAuth token access
    // First line is with OAuth, second is without
    // configureOAuth([ "write:registration" ], createRegistrationLogic);
    createRegistrationLogic();
}

/**
 * Builds a url allowing the learner to access the course.
 *
 * This sample will build the launch link and print it out. It will then
 * pause and wait for user input, allowing you to navigate to the course
 * to generate sample learner progress. Once this step has been reached,
 * hitting the enter key will continue program execution.
 *
 * @param {String} registrationId Id of the registration the link is being built for.
 * @returns {String} Link for the learner to launch the course.
 */
function buildLaunchLink(registrationId, callback) {
    function buildLaunchLinkLogic() {
        const registrationApi = new ScormCloud.RegistrationApi();
        const settings = { redirectOnExitUrl: "Message" };
        registrationApi.buildRegistrationLaunchLink(registrationId, settings, function(error, data) {
            if (error) {
                return logErrorAndCleanUp(error.response.text);
            }

            callback(data.launchLink);
        });
    }

    // (Optional) Further authenticate via OAuth token access
    // First line is with OAuth, second is without
    // configureOAuth([ "read:registration" ], buildLaunchLinkLogic);
    buildLaunchLinkLogic();
}

/**
 * Gets information about the progress of the registration.
 *
 * For the most up-to-date results, you should implement our postback
 * mechanism. The basic premise is that any update to the registration
 * would cause us to send the updated results to your system.
 *
 * More details can be found in the documentation:
 * https://cloud.scorm.com/docs/v2/guides/postback/
 *
 * @param {String} registrationId Id of the registration to get results for.
 * @returns {RegistrationSchema} Detailed information about the registration's progress.
 */
function getResultForRegistration(registrationId, callback) {
    function getResultForRegistrationLogic() {
        const registrationApi = new ScormCloud.RegistrationApi();
        registrationApi.getRegistrationProgress(registrationId, {}, function(error, data) {
            if (error) {
                return logErrorAndCleanUp(error.response.text);
            }

            callback(data);
        });
    }

    // (Optional) Further authenticate via OAuth token access
    // First line is with OAuth, second is without
    // configureOAuth([ "read:registration" ], getResultForRegistrationLogic);
    getResultForRegistrationLogic();
}

/**
 * Gets information about all courses. The result received from the API
 * call is a paginated list, meaning that additional calls are required
 * to retrieve all the information from the API. This has already been
 * accounted for in the sample.
 *
 * @returns {Array<CourseSchema>} List of detailed information about all of the courses.
 */
function getAllCourses(callback) {
    function getAllCoursesLogic() {
        const courseApi = new ScormCloud.CourseApi();
        const courseList = [];

        function getPaginatedCourses(more) {
            // This call is paginated, with a token provided if more results exist.
            // Additional filters can be provided to this call to get a subset
            // of all courses.
            courseApi.getCourses({ more: more },  function(error, data) {
                if (error) {
                    return logErrorAndCleanUp(error.response.text);
                }

                courseList.push(...data.courses);

                if (data.more) {
                    return getPaginatedCourses(data.more);
                }

                callback(courseList);
            });
        }
        getPaginatedCourses(null);
    }

    // (Optional) Further authenticate via OAuth token access
    // First line is with OAuth, second is without
    // configureOAuth([ "read:course" ], getAllCoursesLogic);
    getAllCoursesLogic();
}

/**
 * Gets information about the registration progress for all
 * registrations. The result received from the API call is a paginated
 * list, meaning that additional calls are required to retrieve all the
 * information from the API. This has already been accounted for in the
 * sample.
 *
 * This call can be quite time-consuming and tedious with lots of
 * registrations. If you find yourself making lots of calls to this
 * endpoint, it might be worthwhile to look into registration postbacks.
 *
 * More details can be found in the documentation:
 * https://cloud.scorm.com/docs/v2/guides/postback/
 *
 * @returns {Array<RegistrationSchema>} List of detailed information about all of the registrations.
 */
function getAllRegistrations(callback) {
    function getAllRegistrationsLogic() {
        const registrationApi = new ScormCloud.RegistrationApi();
        const registrationList = [];

        function getPaginatedRegistrations(more) {
            // This call is paginated, with a token provided if more results exist.
            // Additional filters can be provided to this call to get a subset
            // of all registrations.
            registrationApi.getRegistrations({ more: more },  function(error, data) {
                if (error) {
                    return logErrorAndCleanUp(error.response.text);
                }

                registrationList.push(...data.registrations);

                if (data.more) {
                    return getPaginatedRegistrations(data.more);
                }

                callback(registrationList);
            });
        }
        getPaginatedRegistrations(null);
    }

    // (Optional) Further authenticate via OAuth token access
    // First line is with OAuth, second is without
    // configureOAuth([ "read:registration" ], getAllRegistrationsLogic);
    getAllRegistrationsLogic();
}

/**
 * Deletes all of the data generated by this sample.
 * This code is run even if the program has errored out, providing a
 * "clean slate" for every run of this sample.
 *
 * It is not necessary to delete registrations if the course
 * they belong to has been deleted. Deleting the course will
 * automatically queue deletion of all registrations associated with
 * the course. There will be a delay between when the course is deleted
 * and when the registrations for the course have been removed. The
 * registration deletion has been handled here to prevent scenarios
 * where the registration hasn't been deleted yet by the time the
 * sample has been rerun.
 *
 * @param {String} courseId Id of the course to delete.
 * @param {String} registrationId Id of the registration to delete.
 */
function cleanUp(courseId, registrationId) {
    function cleanUpLogic() {
        // This call will use OAuth with the "delete:course" scope
        // if configured.  Otherwise the basic auth credentials will be used
        const courseApi = new ScormCloud.CourseApi();
        courseApi.deleteCourse(courseId, function(error) {
            if (error) {
                throw error;
            }
        });

        // The code below is to prevent race conditions if the
        // sample is run in quick successions.

        // This call will use OAuth with the "delete:registration" scope
        // if configured.  Otherwise the basic auth credentials will be used
        const registrationApi = new ScormCloud.RegistrationApi();
        registrationApi.deleteRegistration(registrationId, function(error) {
            if (error) {
                throw error;
            }
        });
    }

    // (Optional) Further authenticate via OAuth token access
    // First line is with OAuth, second is without
    // configureOAuth([ "delete:course", "delete:registration" ], cleanUpLogic);
    cleanUpLogic();
}

// If running through the browser, call browserFileUpload instead:

// <!DOCTYPE html>
// <html>
//     <body>
//         <p>
//             When running the sample in the browser, a File object is required to be passed to the sample code.
//             Input a file using the input below and then run the sample code.
//         </p>
//         <input id="fileButton" type=file />
//     </br>
//         <button onclick="runSample()">Click here to run sample code in the console.</button>
//         <p id="runLog"></p>
//     </body>
// </html>
// <script src="bundle.js"></script>
// <script>
//     function runSample() {
//         const file = document.getElementById('fileButton').files[0];
//         browserFileUpload(file);
//
//         document.getElementById("runLog").innerHTML += "Sample is running, please see console for output. The process may take a few seconds. <br>";
//     }
// </script>

if (jsEnv.isNode) {
    COURSE_FILE = fs.createReadStream(COURSE_PATH);
    main();
} else {
    window.browserFileUpload = (file) => {
        COURSE_FILE = file;
        main();
    };
}

```
