unity-game-enginewebglunity-webgl

Is there a way to prevent a Unity WebGL build to get permanently cached?


I currently have my Unity WebGL build deployed into one of my company servers. The hosting provider is Aruba.

The issue I'm encountering is that when I release a new build onto the same address (I delete and paste the new build files), the old build files get cached permanently for the users.

So it's necessary for them to hard refresh the browser (usually Chrome) to get the latest version of the game. Also, if they normally refresh after a hard refresh the old build gets loaded instead, so it's necessary to always hard refresh when landing on the page.

I've tried many options, for instance, now I am using versioning to avoid this issue, but that does not prevent anything. I've tried to disable data caching from inside Unity, but I would still like the build to be cached (otherwise the loading time at each session would be impossible to afford), but also when an update comes online the browser should automatically detect that.

This is a sample of what I currently do:

<!DOCTYPE html>
<html lang="en-us">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    ...
    <script src="Build/EyeRidersX.loader.js?v=1.0.5"></script>
        <link rel="stylesheet" href="TemplateData/style.css">
    <link rel="manifest" href="manifest.webmanifest">
  ...
  </head>
  <body>
    <div id="unity-container">
      <canvas id="unity-canvas" width=960 height=600></canvas>
      <div id="unity-loading-bar">
        <div id="unity-logo"></div>
        <div id="unity-progress-bar-empty">
          <div id="unity-progress-bar-full"></div>
        </div>
      </div>
      <div id="unity-warning"> </div>
    </div>
          <div id="unity-progress-bar-full"></div>
        </div>
      </div>
      <div id="unity-warning"> </div>
    </div>
    <script>
      window.addEventListener("load", function () {
        if ("serviceWorker" in navigator) {
          navigator.serviceWorker.register("ServiceWorker.js");
        }
      });

      var container = document.querySelector("#unity-container");
      var canvas = document.querySelector("#unity-canvas");
      var loadingBar = document.querySelector("#unity-loading-bar");
      var progressBarFull = document.querySelector("#unity-progress-bar-full");
      var warningBanner = document.querySelector("#unity-warning");

      function unityShowBanner(msg, type) {
        function updateBannerVisibility() {
          warningBanner.style.display = warningBanner.children.length ? 'block' : 'none';
        }
        var div = document.createElement('div');
        div.innerHTML = msg;
        warningBanner.appendChild(div);
        if (type == 'error') div.style = 'background: red; padding: 10px;';
        else {
          if (type == 'warning') div.style = 'background: yellow; padding: 10px;';
          setTimeout(function() {
            warningBanner.removeChild(div);
            updateBannerVisibility();
          }, 5000);
        }
        updateBannerVisibility();
      }

      var buildUrl = "Build";
      var loaderUrl = buildUrl + "/EyeRidersX.loader.js?v=1.0.5";
      var config = {
        dataUrl: buildUrl + "/EyeRidersX.data?v=1.0.5",
        frameworkUrl: buildUrl + "/EyeRidersX.framework.js?v=1.0.5",
        codeUrl: buildUrl + "/EyeRidersX.wasm?v=1.0.5",
        streamingAssetsUrl: "StreamingAssets",
        ...
        productVersion: "1.1.1",
        showBanner: unityShowBanner,
      };

      ...
    </script>
  </body>
</html>

Solution

  • Unity officially states this:

    The browser automatically stores (caches) certain file types such as .html, .js, .css, .json, .jpg, .png, so they don’t need to be explicitly stored in the WebGL Cache. Typical candidates for the WebGL cache include large files and files that use a custom file format.

    the issue in my case is that one .js (***.framework.js) file is cached and is not updated when a new build is released.

    For this reason, this code that should avoid caching does not work:

    cacheControl: function (url) {
          if (url.match(/\.data/) || url.match(/\.bundle/)) {
              return "must-revalidate";
          }
    
          if (url.match(/\.mp4/) || url.match(/\.custom/)) {
              return "immutable";
          }
      
          return "no-store";
        },
    

    For my web build Progressive Web App (PWA) there's a file called ServiceBuilder.js that rules caching operations. The original file always contained the same name for the cache, which prevented the browser from refreshing all the files. Below I implemented this procedure at each new update of a build I assign a unique version name for each release (something that I wasn't doing) then I check if a previously cached version exists if so I erase it and start the process of caching (obviously .br depends on you compression format, I went for brotli):

    const cacheName = "NameOfTheGame-UpdatedVersion";
    const contentToCache = [
      "***.loader",
      "***.framework.js.br",
      "***.data.br",
      "***.wasm.br",
      "TemplateData/style.css",
    ];
    
    self.addEventListener("install", function (e) {
      console.log("[Service Worker] Install");
    
      e.waitUntil(
        caches.keys().then((cacheNames) => {
          return Promise.all(
            cacheNames
              .filter((name) => {
                return name !== cacheName;
              })
              .map((name) => {
                console.log("[Service Worker] Deleting old cache:", name);
                return caches.delete(name);
              })
          );
        })
      );
    
      e.waitUntil(
        (async function () {
          const cache = await caches.open(cacheName);
          console.log("[Service Worker] Caching all: app shell and content");
          await cache.addAll(contentToCache);
        })()
      );
    });
    
    self.addEventListener("fetch", function (e) {
      e.respondWith(
        (async function () {
          let response = await caches.match(e.request);
          console.log(`[Service Worker] Fetching resource: ${e.request.url}`);
          if (response) {
            return response;
          }
    
          response = await fetch(e.request);
          const cache = await caches.open(cacheName);
          console.log(`[Service Worker] Caching new resource: ${e.request.url}`);
          cache.put(e.request, response.clone());
          return response;
        })()
      );
    });