" MicromOne: Detecting Dynamics 365 Web Resource Updates with JavaScript Hashing

Pagine

Detecting Dynamics 365 Web Resource Updates with JavaScript Hashing

When working with Dynamics 365 / Dataverse Web Resources, one common challenge is ensuring users are aware when a JavaScript or HTML Web Resource has been updated. Browser caching can cause users to unknowingly run outdated versions, leading to unexpected behavior and hard-to-diagnose bugs.

In this article, we’ll look at a simple and effective technique to detect Web Resource changes at runtime using JavaScript hashing and notify users when an update is detected.

The Idea

The core idea is straightforward:

  1. Download the Web Resource content

  2. Calculate a hash (SHA-1) of the content

  3. Store the hash in localStorage

  4. Compare the current hash with the previously stored one

  5. Notify the user if the hash has changed

This approach works entirely on the client side and requires no server-side customization.

Triggering the Check on Form Load

The check is executed when a Dynamics 365 form loads:

formContext.data.addOnLoad(
  Opportunity.checkWebResourceHash.bind(this, "opportunity_webresource")
);

This ensures the verification runs automatically whenever the form is opened.

The Hash Comparison Function

Here’s the full implementation of the function responsible for detecting changes:

checkWebResourceHash: async function (webResourceName) {
  const STORAGE_KEY = `WR_HASH_${webResourceName}`;
  const STORAGE_DATE_KEY = `WR_HASH_DATE_${webResourceName}`;

  try {
    const clientUrl = Xrm.Utility.getGlobalContext().getClientUrl();
    const url = `${clientUrl}/WebResources/${webResourceName}`;

    // Fetch the Web Resource content without using cache
    const response = await fetch(url, { cache: "no-store" });
    const text = await response.text();

    // Generate SHA-1 hash
    const encoder = new TextEncoder();
    const data = encoder.encode(text);
    const hashBuffer = await crypto.subtle.digest("SHA-1", data);

    const newHash = Array.from(new Uint8Array(hashBuffer))
      .map(b => b.toString(16).padStart(2, "0"))
      .join("");

    const oldHash = localStorage.getItem(STORAGE_KEY);

    // Detect changes
    if (oldHash && oldHash !== newHash) {
      const now = new Date().toISOString();

      localStorage.setItem(STORAGE_KEY, newHash);
      localStorage.setItem(STORAGE_DATE_KEY, now);

      Xrm.Navigation.openAlertDialog({
        title: "Web Resource Update",
        text:
          `The Web Resource "${webResourceName}" has been updated.\n\n` +
          `Date: ${new Date(now).toLocaleString()}`
      });
    }

    // First-time initialization
    if (!oldHash) {
      localStorage.setItem(STORAGE_KEY, newHash);
      localStorage.setItem(STORAGE_DATE_KEY, new Date().toISOString());
    }

  } catch (e) {
    console.error("Error while checking Web Resource", webResourceName, e);
  }
}

Why SHA-1?

SHA-1 is not recommended for security purposes, but in this scenario it’s perfectly adequate:

  • We’re not securing sensitive data

  • We only need a fast and consistent checksum

  • It’s widely supported by the Web Crypto API

If you prefer, you can easily switch to SHA-256 by replacing "SHA-1" with "SHA-256".

Benefits of This Approach

  • No server-side changes

  • Works with any JavaScript or HTML Web Resource

  • Prevents silent cache-related issues

  • Improves transparency for users and testers

  • Easy to reuse across multiple forms and entities

Possible Enhancements

  • Automatically reload the page after detection

  • Display the last update date in a custom notification

  • Store hashes per environment (Dev / Test / Prod)

  • Extend the logic to multiple Web Resources at once