Asset Uploading for Web

This feature is currently in beta. As a result, you may encounter bugs or unexpected behavior. Functionality is subject to change. If you'd like to use this feature on your account reach out to your Customer Success Manager or support@fullstory.com to get access.

Introduction

Web pages are built up from many parts: the DOM, CSS, images, and fonts. The FullStory recording script captures the DOM directly and includes it in the recording, but it does not capture external assets that are referenced from the DOM by URL. For example, imagine that the recording script encounters an image, like this:

<img src="https://example.com/image.png">

The URL “https://example.com/image.png” will appear in the recording, but the image itself will not. Instead, when FullStory’s servers process the recording, they will recognize this URL and fetch the asset, making it available for playback.

In certain cases, this process breaks down, because FullStory’s servers are unable to fetch the asset. This can happen for a variety of reasons:

  • The asset may be unavailable without authentication. Because FullStory does not record cookies, our servers are unable to access the asset in this case.
  • The asset may not be available over the internet at all. For example, this can happen for assets that are bundled with Electron or Ionic apps and are accessed via the file system.
  • The asset may have been deleted by the time FullStory’s servers attempt to fetch it. Sites which rapidly deploy a series of new versions in quick succession can sometimes encounter this issue.

To handle these situations, FullStory offers an alternative approach: asset uploading. This feature allows you to push assets directly to FullStory’s servers. With this feature, FullStory will no longer attempt to fetch these assets over the internet and, as long as assets are uploaded whenever a new version of your site is deployed, FullStory playback will always have access to everything it needs to provide a high quality rendering of every session.

Installing the Asset Uploader

To make use of asset uploading, you’ll need to install the fullstory-asset-uploader tool on your local machine. Versions are available for the following platforms:

  • macOS
  • x86-64 Linux
Note: During the beta period, you’ll need to contact us to obtain the tool. Additionally, the macOS version of the tool is not code signed during the beta period.

Depending on your system’s configuration, running it may result in an error message stating “fullstory-asset-uploader cannot be opened because the developer cannot be verified.”  You can resolve this by running the following command after decompressing the tool:

xattr -d com.apple.quarantine fullstory-asset-uploader

Generating an API Key

To actually upload assets, you’ll need an API key. You can generate one by logging into your FullStory account and visiting the “API Keys” section of your account settings. During the beta period, uploading assets requires an API key with admin permissions.

More details about API key management can be found in our documentation.

Creating a Simple Asset Map

To make use of uploaded assets, FullStory’s servers need more than just the files themselves. We also need to know which URL(s) each asset corresponds to in recordings, so that we can wire everything together. To specify this mapping, you’ll need to create an asset map, which is just a file in JSONC format - essentially, JSON with comments. This is what a simple asset map looks like:

{
  // A mapping from URLs to file paths.
  "assets": {
    "https://example.com/images/image.png": {
      "path": "site-assets/image.png"
    },
    "https://example.com/headers/masthead.png": {
      "path": "site-assets/masthead.png"
    }
  },

  // The asset map format this file uses. Only version 2 is supported for web
  // asset uploading.
  "version": "2"
}

The two top-level properties in this example, “assets” and “version”, are the only required properties in an asset map. In this example, each entry in “assets” maps a URL to a path on the file system. These paths are interpreted relative to the location of the asset map itself, so you’ll want to place the asset map at the root of the directory tree that contains your assets.

It’s worth noting that, in the real world, you probably won’t write your asset map by hand. We recommend writing some code to generate it as a side effect of your build process; often this code will run as a part of your configuration for a tool like gulp or webpack.

Uploading Assets to FullStory

Once you have an asset map containing your assets, you can use fullstory-asset-uploader to upload them to FullStory, using a command that looks like this:

fullstory-asset-uploader upload <ASSET_MAP.JSON\> --api-key <FULLSTORY_API_KEY>

If you prefer, you can specify the API key by setting a FULLSTORY_API_KEY environment variable instead.

The tool's output will look similar to this:

Note the hash value at the end of the output, which starts with “sha256:”. This hash value is referred to as an “asset map id”. It uniquely identifies both the asset map and the content of every asset it includes. If you’re familiar with git, the asset map id is the equivalent of a commit hash.

The asset map id is used to tell the FullStory recording script which asset map it should use for a given recording. We’ll discuss how that works in the next section but, before you can use the asset map id, you’ll need to capture it.

If you’re uploading your assets manually, you can just copy the asset map id and paste it where it needs to go but, in the real world you’ll probably be running fullstory-asset-uploader via a script that runs as part of your build or deployment process. When fullstory-asset-uploader successfully uploads your assets, it returns a zero exit code and prints the asset map id to stdout. If it fails, it returns a non-zero exit code. Regardless of success or failure, all of the informational output goes to stderr, so it won’t interfere with your ability to capture the asset map id.

Linking an Asset Map to Your Recordings

As your site changes over time, you’ll upload many different asset maps. You may also have different asset maps for different sites that you operate, or for different A/B test treatments, or for different versions of an Electron or Ionic app. In many of these situations, there will be several different “active” asset maps at once. This means that you need to tell the FullStory recording script which asset map id should be linked to each recording.

There are two different approaches you can use to do this; one or the other may be more convenient depending on the details of your build process.

Using a Global Variable

One option is to set the _fs_asset_map_id global variable using a line of JavaScript that looks like this:

window['_fs_asset_map_id'] = 'ASSET_MAP_ID';

You can generate a separate <script> element containing this line of code and place it in the <head> of your page, before the FullStory snippet, or you can include it in the snippet itself:

  <script>
    window['_fs_debug'] = false;
    window['_fs_host'] = 'fullstory.com';
    window['_fs_script'] = 'edge.fullstory.com/s/fs.js';
    window['_fs_org'] = 'XXXXX';
    window['_fs_namespace'] = 'FS';
    window['_fs_asset_map_id'] = 'ASSET_MAP_ID';
    …
  </script>

This approach is usually the most convenient, because it doesn’t require modifying your main JavaScript bundle. This can result in a simpler build and deployment process.

Using the API

You can also use the FS.setVars() API using a line of JavaScript that looks like this:

FS.setVars('document', { assetMapId: 'ASSET_MAP_ID' });

Understanding Document Scope

Regardless of which of the two approaches above you take, the effect is ultimately to set a document-scope FullStory variable called assetMapId. (This is why the API call includes the string “document”). Just as the user-scope FullStory variables you may be familiar with are associated with the active user, document-scope variables are associated with the active document. In other words, once set, these document-scoped variables will be present until the user navigates to a new page.

The fact that document-scope variables are associated with the document means that they persist even if you use APIs like FS.shutdown() and FS.restart() to initialize a new recording. This means that you can set assetMapId once at startup and never think about it again.

It’s also important to know that document-scope FullStory variables can only be set once. Once the assetMapId for a page is set, you can’t change it.

Separating Build and Deployment

In practice, most customers separate their build process (which includes generating JavaScript bundles) from their deployment process (which includes publishing those JavaScript bundles to their live site). It often doesn’t make sense to actually upload assets to FullStory during the build process, especially since the same build process is often used for local development. To facilitate this, fullstory-asset-uploader offers an additional command generate:

fullstory-asset-uploader generate <ASSET_MAP.JSON>

This command generates an asset map id just like the upload command, but it doesn’t actually upload the asset map or the assets themselves to FullStory’s servers. Note that you don’t need an API key to run this command.

It often makes sense to use the generate command as part of your build process to generate JavaScript code that includes the asset map id, then use the upload command during your deployment process so that FullStory has the necessary assets once the site goes live.

URL Templates

The simple asset map format described above has a limitation: you need to explicitly specify the URL of every asset in the asset map. At times, this is inconvenient. For example, you may have both staging and production sites which include all the same assets. Rather than list each asset twice with different URLs, or create two different asset maps, you can use URL templates to associate the same asset with different URLs in each recording.

A URL template can appear wherever a URL would appear in an asset map. Here’s what a URL template looks like:

https://${env}.${domain}/images/image.png

URL templates use the ${template string} syntax you may be familiar with from JavaScript to interpolate the values of one or more template parameters into a URL. The values of these template parameters are set using JavaScript code that runs on your website. Different recordings may use different values for these parameters. FullStory’s servers substitute the parameter values in the recording into each URL template to produce a final URL for each asset in your asset map.

In the following sections, we’ll describe this process in more detail.

Declaring Template Parameters

To use URL templates in your asset map, you must first declare the parameters that they use by adding a “parameters” property to your asset map. This empty asset map declares two parameters, “domain” and “env”:

{
  "parameters": {
    "domain": {...},
    "env": {...}
  },
  "assets": {...},
  "version": "2"
}

Each entry in the “parameters” object defines a parameter; the key is the parameter’s name, and the value is an object that defines options for that parameter. There are no required options, so the options object may be totally empty.

Adding URL Templates to an Asset Map

Once your parameters are defined, you can use them in any URL in your asset map, converting that URL into a URL template:

{
  "parameters": {
    "domain": {...},
    "env": {...}
  },
  "assets": {
    // A URL template that uses the "domain" parameter.
    "https://www.${domain}/images/image1.png": {
      "path": "site-assets/image1.png"
    },
    // A URL template that uses both parameters.
    "https://${env}.${domain}/images/image2.png": {
      "path": "site-assets/image2.png"
    },
    // An asset map can mix URLs and URL templates freely.
    "https://www.example.com/images/image3.png": {
      "path": "site-assets/image3.png"
    }
  },
  "version": "2"
}

Setting Template Parameter Values

In order to use URL templates, you must add JavaScript code to your site to define the value of each parameter. URL template parameters take their value from document-scope FullStory variables with the same name. This example code sets the “domain” and “env” variables:

FS.setVars('document', {
  domain: 'example.com',
  env: 'staging'
});

The values of these variables will be included in the recording. When FullStory’s servers process the recording, the values are substituted for the parameters of the same name wherever they appear in a URL template in the asset map. For example, this URL template in the asset map above:

https://${env}.${domain}/images/image.png

Will be transformed to this URL after the variables in the JavaScript snippet above are substituted in:

https://staging.example.com/images/image.png

For a recording that uses the asset map in the previous section and includes the variable values above, FullStory’s servers will behave as if the asset map looked like this:

{
  "assets": {
    "https://www.example.com/images/image1.png": {
      "path": "site-assets/image1.png"
    },
    "https://staging.example.com/images/image2.png": {
      "path": "site-assets/image2.png"
    },
    "https://www.example.com/images/image3.png": {
      "path": "site-assets/image3.png"
    }
  },
  "version": "2"
}

Be aware that, like assetMapId and all document-scope variables, URL template parameters can only be set once for a given page.

Unset Template Parameters and Default Values

If you don’t provide a value for a URL template parameter via the FS.setVars() API, URL templates that reference that parameter will be ignored for that recording. You will usually want to specify values for every parameter you use. However, you have the option of providing a default value for a parameter which will be used if you don’t set the corresponding document-scope FullStory variable. The default value is specified using a “default” property in the parameter declaration’s options object. The asset map syntax looks like this:

{
  "parameters": {
    "env": { "default": "prod" }
  },
  "assets": {
    "https://${env}.example.com/images/image.png": {
      "path": "site-assets/image.png"
    }
  },
  "version": "2"
}

Built-in Template Parameters

It’s quite common to use the document’s domain, or more generally the document’s origin (which includes the protocol and port), as a URL template parameter. To simplify this common case, FullStory provides a built-in template parameter called fs:origin.

For example, if you record a page with this URL: https://dev.example.com:8080/images/image.png.

Thee value of fs:origin will be: https://dev.example.com:8080.

Here’s an example of how to use it:

{
  "parameters": {
    // You need to declare even built-in parameters.
    "fs:origin": { }
  },
  "assets": {
    "${fs:origin}/images/image.png": {
      "path": "site-assets/image.png"
    }
  },
  "version": "2"
}

Currently, fs:origin is the only built-in template parameter. Any new built-in parameters we add in the future will also start with an fs: prefix, so you don’t need to worry about their names conflicting with the parameters that you define.

Need to get in touch with us?

The FullStory Team awaits your every question.

Contact us