The cmi5 specification calls for a “fetch” parameter to be added to the launch URL when opening and Assignable Unit (AU). What is the cmi5 fetch parameter? How is it used? Are there any “gotchas”? In this article I will attempt to end the confusion.
Author’s Note: This is a technical article aimed at content tool developers, or those that are creating cmi5 AU by hand.
A protip by poupougnac about express, route, url, javascript, parameters, and nodejs. Second, to get the response body, we need to use an additional method call. Response provides multiple promise-based methods to access the body in various formats. Response.text – read the response and return as text, response.json – parse the response as JSON, response.formData – return the response as FormData object (explained in the next chapter).
What is the fetch Parameter?
The cmi5 fetch parameter is a URL that must be called by the AU. The fetch URL must be used by the AU to get an authorization token. This token is then placed in the Authorization header for all HTTP requests made to the xAPI endpoint. In other words, an AU’s authorization to write to the Learning Record Store (LRS) is via the token retrieved from the fetch URL.
The fetch Parameter Rules
There just a few rules around the fetch parameter:
- The AU may call the URL only ONCE per session. Subsequent calls to the URL by the AU will receive an error.
- The rule above indicates the LMS must generate a unique fetch URL for each launch of an AU.
The Gotcha
There is an obvious “gotcha” in rule number 1 above. What if the user refreshes their browser in the middle of an AU? If the content is unprepared for this event, it will attempt to call the fetch URL again, get an error and likely display that error to the user. At this point the user is basically dead in the water because the AU has lost authorization to write to the LRS.
There is a simple solution for this and it is called “sessionStorage.” All major browsers support the ability to store data locally with the sessionStorage object. Here is a simple script, using jquery, to first check if the required token exists in sessionStorage, and if not then go ahead and get it from the fetch URL (Warning, code ahead).
$(function() {
// First, Read the fetch url parameters
var fetchUrl = parse('fetch');
// Next, check if this value exists in sessionStorage.
var token = sessionStorage.getItem(fetchUrl);
// Was the token found in sessionStorage?
if (!token) {
// Call the fetch url to get the token
$.post(fetchUrl, null, function(t) {
token = t['auth-token'];
// Save the token to sessionStorage
sessionStorage.setItem(fetchUrl, token);
Why does this work? The fetch parameter is passed on AU launch URL. If the browser is refreshed, the fetch parameter is unchanged. So we use the fetch URL as the “name” property in our call to sessionStorage.setItem(). Then, if the browser is refreshed we can easily find the previously obtained authorization token with a call to sessionStorage.getItem().
For more information on cmi5, be sure and check out this blog’s xAPI and cmi5 category.
Art Werkenthin is president of RISC, Inc. and has over 30 years' experience working with LMS systems in the Oil & Gas, Retail, Finance and other industries. Mr. Werkenthin holds a B.S. in Electrical Engineering and an M.B.A. in Information Systems Management from the University of Texas. Mr. Werkenthin is a member of the ADL cmi5 committee and frequently presents on cmi5 and xAPI. Follow him on Twitter @AWerkenthin for xAPI and cmi5 updates, as well as blog post announcements.
![Fetch Fetch](https://miro.medium.com/max/4424/1*6rQrIcpkumrUlkyrNcSXbg.png)
Chrome Dev Summit 2020 is back & going virtual on December 9-10. Learn more
We're in the process of restructuring our PWA training resources.
You can use the materials linked to from this page, but some of the content may be out of date.
We're still working on updating written materials, but check out our new codelabs and videos.
Codelab: Fetch API
What is fetch?
The Fetch API is a simple interface for fetching resources. Fetch makes it easier to make web requests and handle responses than with the older XMLHttpRequest, which often requires additional logic (for example, for handling redirects).
Note: Fetch supports the Cross Origin Resource Sharing (CORS). Testing generally requires running a local server. Note that although fetch does not require HTTPS, service workers do and so using fetch in a service worker requires HTTPS. Local servers are exempt from this.You can check for browser support of fetch in the window interface. For example:
main.js
There is a polyfill for browsers that are not currently supported (but see the readme for important caveats.).
The fetch() method takes the path to a resource as input. The method returns a promise that resolves to the Response of that request.
Making a request
Let's look at a simple example of fetching a JSON file:
main.js
We pass the path for the resource we want to retrieve as a parameter to fetch. In this case this is examples/example.json. The fetch call returns a promise that resolves to a response object.
When the promise resolves, the response is passed to
.then
. This is where the response could be used. If the request does not complete, .catch
takes over and is passed the corresponding error.Response objects represent the response to a request. They contain the requested resource and useful properties and methods. For example,
response.ok
, response.status
, and response.statusText
can all be used to evaluate the status of the response.Evaluating the success of responses is particularly important when using fetch because bad responses (like 404s) still resolve. The only time a fetch promise will reject is if the request was unable to complete. The previous code segment would only fall back to .
catch
if there was no network connection, but not if the response was bad (like a 404). If the previous code were updated to validate responses it would look like:main.js
Now if the response object's
ok
property is false (indicating a non 200-299 response), the function throws an error containing response.statusText
that triggers the .catch
block. This prevents bad responses from propagating down the fetch chain.Reading the response object
Responses must be read in order to access the body of the response. Response objects have methods for doing this. For example, Response.json() reads the response and returns a promise that resolves to JSON. Adding this step to the current example updates the code to:
main.js
This code will be cleaner and easier to understand if it's abstracted into functions:
main.js
(This is promise chaining.)
To summarize what's happening:
Step 1. Fetch is called on a resource, examples/example.json. Fetch returns a promise that will resolve to a response object. When the promise resolves, the response object is passed to
validateResponse
.Step 2.
Note: You can also handle any network status code using the validateResponse
checks if the response is valid (is it a 200-299?). If it isn't, an error is thrown, skipping the rest of the then
blocks and triggering the catch
block. This is particularly important. Without this check bad responses are passed down the chain and could break later code that may rely on receiving a valid response. If the response is valid, it is passed to readResponseAsJSON
.status
property of the response
object. This lets you respond with custom pages for different errors or handle other responses that are not ok
(i.e., not 200-299), but still usable (e.g., status codes in the 300 range). See Caching files with the service worker for an example of a custom response to a 404.Step 3.
readResponseAsJSON
reads the body of the response using the Response.json() method. This method returns a promise that resolves to JSON. Once this promise resolves, the JSON data is passed to logResult
. (Can you think of what would happen if the promise from response.json()
rejects?)Step 4. Finally, the JSON data from the original request to examples/example.json is logged by
logResult
.For more information
Example: fetching images
Let's look at an example of fetching an image and appending it to a web page.
main.js
In this example an image (examples/kitten.jpg) is fetched. As in the previous example, the response is validated with
Note: The URL object'screateObjectURL() method is used to generate a data URL representing the Blob. This is important to note as you cannot set an image's source directly to a Blob. The Blob must first be converted into a data URL.validateResponse
. The response is then read as a Blob (instead of as JSON), and an image element is created and appended to the page, and the image's src
attribute is set to a data URL representing the Blob.For more information
Example: fetching text
Let's look at another example, this time fetching some text and inserting it into the page.
main.js
In this example a text file is being fetched, examples/words.txt. Like the previous two exercises, the response is validated with
Note: It may be tempting to fetch HTML and append that using the validateResponse
. Then the response is read as text, and appended to the page.innerHTML
attribute, but be careful -- this can expose your site to cross site scripting attacks!For more information
Note: For completeness, the methods we have used are actually methods of Body, a Fetch API mixin that is implemented in the Response object.Making custom requests
fetch()
can also receive a second optional parameter, init
, that allows you to create custom settings for the request, such as the request method, cache mode, credentials, and more.Example: HEAD requests
By default fetch uses the GET method, which retrieves a specific resource, but other request HTTP methods can also be used.
HEAD requests are just like GET requests except the body of the response is empty. You can use this kind of request when all you want the file's metadata, and you want or need the file's data to be transported.
To call an API with a HEAD request, set the method in the
init
parameter. For example:main.js
Snow leopard software download. This will make a HEAD request for examples/words.txt.
You could use a HEAD request to check the size of a resource. For example:
main.js
Here the HEAD method is used to request the size (in bytes) of a resource (represented in the content-length header) without actually loading the resource itself. In practice this could be used to determine if the full resource should be requested (or even how to request it).
Example: POST requests
Fetch can also send data to an API with POST requests. The following code sends a 'title' and 'message' (as a string) to someurl/comment:
main.js
Note: In production, remember to always encrypt any sensitive user data.Reverse gamertag lookup youtube. The method is again specified with the
init
parameter. This is also where the body of the request is set, which represents the data to be sent (in this case the title and message).The body data could also be extracted from a form using the FormData interface. For example, the above code could be updated to:
Fetch Get Request
main.js
Custom headers
The
init
parameter can be used with the Headers interface to perform various actions on HTTP request and response headers, including retrieving, setting, adding, and removing them. An example of reading response headers was shown in a previous section. The following code demonstrates how a custom Headers object can be created and used with a fetch request:main.js
Here we are creating a Headers object where the
Note: Only some headers, like Content-Type
header has the value of text/plain
and a custom X-Custom-Header
header has the value of hello world
.Content-Type
can be modified. Others, like Content-Length
and Origin
are guarded, and cannot be modified (for security reasons).Fetch Get Params
Custom headers on cross-origin requests must be supported by the server from which the resource is requested. The server in this example would need to be configured to accept the
X-Custom-Header
header in order for the fetch to succeed. When a custom header is set, the browser performs a preflight check. This means that the browser first sends an OPTIONS
request to the server to determine what HTTP methods and headers are allowed by the server. If the server is configured to accept the method and headers of the original request, then it is sent. Otherwise, an error is thrown.For more information
Cross-origin requests
Fetch (and XMLHttpRequest) follow the same-origin policy. This means that browsers restrict cross-origin HTTP requests from within scripts. A cross-origin request occurs when one domain (for example http://foo.com/) requests a resource from a separate domain (for example http://bar.com/). This code shows a simple example of a cross-origin request:
main.js
Note: Cross-origin request restrictions are often a point of confusion. Many resources like images, stylesheets, and scripts are fetched cross-origin. However, these are exceptions to the same-origin policy. Cross-origin requests are still restricted from within scripts.There have been attempts to work around the same-origin policy (such as JSONP). The Cross Origin Resource Sharing (CORS) mechanism has enabled a standardized means of retrieving cross-origin resources. The CORS mechanism lets you specify in a request that you want to retrieve a cross-origin resource (in fetch this is enabled by default). The browser adds an
Origin
header to the request, and then requests the appropriate resource. The browser only returns the response if the server returns an Access-Control-Allow-Origin
header specifying that the origin has permission to request the resource. In practice, servers that expect a variety of parties to request their resources (such as 3rd party APIs) set a wildcard value for the Access-Control-Allow-Origin
header, allowing anyone to access that resource.If the server you are requesting from doesn't support CORS, you should get an error in the console indicating that the cross-origin request is blocked due to the CORS
Access-Control-Allow-Origin
header being missing.You can use
no-cors
mode to request opaque resources. Opaque responses can't be accessed with JavaScript but the response can still be served or cached by a service worker. Using no-cors
mode with fetch is relatively simple. To update the above example with no-cors
, we pass in the init
object with mode
set to no-cors
: