Developer Overview
Engine 20.x brings Offline to the authenticated API and provides a better way to sync with Engine either directly from the app, or using your server as the central point for communications. There are a few other big improvements in this update as well.
Perhaps the biggest improvement is in how we handle the offline course packages. We’ve separated the player files from the content packages so you no longer have to update the packages on the devices when you update Engine. You can keep your current package versions on the device and just update the player files when there is an Engine update.
The API additions include resources to get the updated player files as a ZIP file and a new way to get the current course version package as a ZIP file. The new API resources provide a much better way to get the current player configuration as well as sending the status back to Engine.
Engine Auth Tokens
All of the following calls use auth tokens for Engine’s API. You can use the /appManagement/token
endpoint to create these tokens.
This allows for the credentials for Engine to be safely stored and not sent to the device or other middleware implementations. This is preferred over using permanent Basic Auth credentials for most API calls from the device.
Getting SCORM Player Files From Engine
Since we have broken the player files out of the course packages, you must use the /player/zip
resource to get the current player files on the device. Here is an example of this call:
curl --location --request GET 'http://[your-engine-instance]/api/v2/player/zip?player=modern' \
--header 'engineTenantName: default' \
--header 'Authorization: Bearer [auth token]’
Note: you will need to configure the FilePathToEngineRoot
setting in your RusticiEngineSettings file to specify the file path to the Engine application folder (e.g., /var/lib/tomcat9/webapps/RusticiEngine
). This is required so that we can zip it up for the API response mentioned above.
Getting Content Files From Engine
This process has now been moved into the authenticated API and can be found at /courses/{courseId}/zip
. This will return the ZIP file containing all of the course files stored in Engine for playback on the device.
The first time a course is requested will take a little longer as we gather the files for the one-time ZIP creation. All subsequent calls for this course will download the same ZIP unless the files have been changed since the initial creation.
Note: You must have the configuration setting PlayerIsAvailableOffline
set to true
for all courses you want to be available through this endpoint. You can configure this on a per-course basis through the API, or if you want all courses to be accessible, then you can add this setting to your configuration file.
Here is an example of this call:
Get ZIP of latest version:
curl --location --request GET 'http://[your-engine-instance]/api/v2/courses/[courseId]/zip?exportType=OFFLINE' \
--header 'engineTenantName: default' \
--header 'Authorization: Bearer [auth token]’
Get ZIP of specific version:
curl --location --request GET 'http://[your-engine-instance]/api/v2/courses/[courseId]/versions/[versionId]/zip?exportType=OFFLINE' \
--header 'engineTenantName: default' \
--header 'Authorization: Bearer [auth token]’
Getting Player Configuration From Engine
The player needs some information about the current state of the learner’s Registration in order to play back the content and record the results. This is all stored in our Player Configuration object and is loaded from the filesystem (configuration.js) at the time of the course launch on the device.
The Player Configuration can be retrieved from the /player/configuration
end point with the following JSON body example:
curl --location --request POST 'http://[your-engine-instance]/api/v2/player/configuration' \
--header 'engineTenantName: default' \
--header 'Authorization: Bearer [auth token]’ \
--header 'Content-Type: application/json' \
--data-raw '{
"registrationId": "[registrationId]", "instance": 0,
"isTracking": true,
"forOffline": true, "includeRegistration": true
}'
The result from this call's legacyData
property should be stored on the device in the local database, as shown in the sample code.
The path to the course on the device should be updated when retrieving it from Engine as shown in the sample code. Engine provides a placeholder value ({rscpRelativePathToCourseFromPlayer}
) for easy string replacement.
This allows you to store the content in different places on the device depending upon the app framework and other requirements.
Launching the Offline Content
Before launching the content, you must pull the configuration data from the local database and write it out to the configuration.js file in the player directory. This is done at launch time so that any updates to the configuration data made during previous launches is reflected in the configuration.js file.
Next, to launch the content, simply point a web view in your app to the player directory’s modern.html
page with parameters like this:
file://[path-to-player-files]/modern.html?configuration=&cc=en&cache=[engine-version]&playerConfUrl=configuration.js
This will open the player and tell it to use the configuration.js file to see where to find the content. This content path value was set by updating the placeholder when getting the Player Configuration from Engine in the previous step.
If you have a blank page, or other errors in the webview, this typically means there is an issue with the configuration.js file in the player directory and you should check it for valid data.
Gathering the Results
When the SCORM course is properly exited, the player will navigate to a blank offline-results.html
page in the player directory. This page is the key to getting the data for the next launch and syncing with Engine. The LocalStorage of this page will contain two items: configurationJs
contains the updated Player Configuration data that you need to launch again, and the runtimeXml
is the data that you need to sync back with Engine.
You will take this data from the LocalStorage, and store them with the proper keys in your local database on the device for later use.
Syncing With Engine
In Engine 20+, you now will use the /player/results
endpoint to send the runtimeXML back to Engine for processing. Here is an example of the JSON body for this POST:
curl --location --request POST 'https://[your.engine.instance]/api/v2/player/results' \ --header 'engineTenantName: default' \
--header 'Authorization: Bearer [auth token]' \
--header 'Content-Type: application/json' \
--data-raw '{
"registrationId":"[registrationId]",
"isExitPlayer":true,
"legacyData":"[runtimeXml example:<?xml version=\"1.0\"?><RTD ... </RTD>]",
"instance":0
}'
IMPORTANT: Once you sync with Engine, you MUST retrieve the Player Configuration again to avoid Launch History conflicts on future syncs. You cannot use the same Player Configuration from before the sync with Engine once the data is saved. Getting a new Player Configuration after a successful sync will ensure a new Launch History ID is created and available for your next sync. You will need to replace the placeholder for the content path as well before saving to the database, as noted earlier.
Checking Current Registration State on Engine
If you need to get further results from Engine at a later date, then you will need to call the /api/v2/registration/{registrationId}
endpoint in Engine while passing the proper registration ID. This will return various levels of information depending upon the flags passed in on the API call.
Simple Database Schema for the App
At a minimum, you will need to track these data items in your application's database.
OfflineCourseState
- CourseId (the CourseId associated with the course in Engine)
- Version (the version of the course in Engine)
- RegistrationId (the pre-existing RegistrationId associated with the learner's attempt in Engine)
- PlayerConfiguration (the js string response in
LegacyData
from the/player/configration
endpoint for the Registration, initially, and theconfigurationJs
value from LocalStorage after launching and exiting the content on the device) - RuntimeXml (the
runtimeXml
value from LocalStorage after exiting the content) - SyncDate (when this record was last synced with Engine)
- LastUpdate (when this record was last updated with PlayerConfiguration or RuntimeXml)
Sample App Code
We can provide an example application for iOS, Android, or Xamarin (C#) upon request. If you need to do offline SCORM in another platform, let us know, and we may be able to help.
Server-Side Sample Code
Our sample code package now includes a server-side example of how to get API tokens for your device and some example course list data to send so the device has what it needs to operate. This example is very simple and uses the HAPI framework to quickly create a REST API for the device to connect with.
This part can be handled in any way you like and this is just an example that we use for testing and development purposes.
Using Your Server Application As Middleware
Many customers have selected not to have their mobile apps connect directly to Engine, and have instead opted to create custom APIs that the mobile app uses with the main app server that it uses for everything else. This main app server would communicate with Engine and handle all of those processes.
This does simplify things on the device quite a bit and can be helpful in ensuring the mobile app gets the latest Player Configuration for all downloaded courses before going offline. This would typically be done at the same time the app connects to get the available content for a learner.
In order to do this, you must implement all of the previously mentioned API resources on your API, so the values can be passed to your server and then relayed to Engine. Let us know if you need additional information on this process as it will be custom to your own implementation.