Electron Integration

Pyze JavaScript SDK integration guide for Electron desktop applications

Overview

An Electron application draws its user interface in Chromium windows, so the Pyze Web SDK runs in an Electron app the same way it runs in a browser. Once the SDK is loaded in a window, Pyze automatically captures page views and user interactions, and you can track custom events and user profiles.

Prerequisites: Create a Pyze app and obtain your App Key before proceeding.

There are two ways to add Pyze. Choose by whether you can change the application’s source code.

Option Use when
A. Add Pyze JavaScript to your application You build the application
B. Add Pyze to the packaged application You install an application built by someone else

Option A – Add Pyze JavaScript to Your Application

Step 1: Add the SDK

Add Pyze JavaScript to your application: paste the following between the <head> and </head> tags of every HTML page your application loads in a window, and fill in pyzeConfig:

<script type="text/javascript">
	var pyzeConfig = {
	  "appKey": "YOUR_PYZE_APP_KEY",
	  "whitelistedURLs": ["https://*.example.com/*"]
	};
	var pyzeAppKey = pyzeConfig.appKey;
	!function(e,t){var s=e.Pyze||{_q:[]},i=e.PyzeEvents||{_q:[]},n=e.PyzeCuratedEvents||{_q:[]},r=e.PyzeIdentity||{_q:[]};function p(t,e){function s(e){t[e]=function(){t._q.push([e].concat(Array.prototype.slice.call(arguments,0)))}}for(var i=0;i<e.length;i++)s(e[i])}p(s,["initialize","getTimerReference","getAppInstanceId","postWebAppVersion","postPageView","setUserOptOut","deleteUser"]),p(i,["postCustomEvent","startTimerForEvent","postCustomEventWithAttributes","postTimedEvent","postTimedEventWithAttributes","postExplicitActivation"]),p(n,["post"]),p(r,["setUserProfile","resetUserProfile","updateUserProfile"]);var o=t.createElement("script"),a=t.getElementsByTagName("script")[0];o.type="text/javascript",o.async=!0,o.src="https://cdn.pyze.com/pyze.js",a.parentNode.insertBefore(o,a),e.Pyze=s,e.PyzeEvents=i,e.PyzeCuratedEvents=n,e.PyzeIdentity=r}(window,document),Pyze.initialize(pyzeAppKey);
</script>
  • appKey: the App Key from your Pyze account.
  • whitelistedURLs: address patterns of the pages Pyze runs on, for example https://*.example.com/*, app://myapp/*, or file://* for pages loaded from the application’s own files. * matches one part of an address, or the rest of it when it ends the pattern.

Step 2: Allow Pyze in your Content Security Policy

If your pages define a Content Security Policy (CSP), add the following to it:

Directive Add Used for
script-src 'unsafe-inline' Running Pyze JavaScript, which is an inline script
script-src https://cdn.pyze.com Downloading the Pyze SDK
connect-src https://unifiedcollector.pyze.com Sending events to Pyze

For example:

<meta
	http-equiv="Content-Security-Policy"
	content="default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.pyze.com; connect-src 'self' https://unifiedcollector.pyze.com"
/>

If you set the policy as a response header from the main process, make the same additions there.

If you would rather not download the SDK from the Pyze CDN, save https://cdn.pyze.com/pyze.js next to your HTML page and replace https://cdn.pyze.com/pyze.js in Pyze JavaScript with ./pyze.js. https://cdn.pyze.com is then not needed in script-src, but you have to update the saved copy yourself when Pyze releases a new SDK.

Then verify the installation.


Option B – Add Pyze to the Packaged Application

Use this option when you cannot change the application’s source code, for example when the application is supplied by another company. You add Pyze to the installed application package when you deploy it.

An Electron application keeps its code in a package, usually a single file named app.asar. You add three files to it: the Pyze SDK, a startup script that loads the SDK into the application’s pages and then starts the application as normal, and a configuration file with your App Key and the pages to cover. The application’s own files are not edited, apart from one line in its package.json.

Before you start

  • You need Node.js 22.12 or later on the computer where you prepare the package. With an older Node.js, use @electron/fuses@1 and @electron/asar@3 in the commands below.

  • Locate the package: resources\app.asar in the installation folder on Windows, Contents/Resources/app.asar inside the .app bundle on macOS. Keep a copy of the original file.

  • Check that the application accepts a modified package:

    npx @electron/fuses read --app "/path/to/the/application"
    

    If the output shows EnableEmbeddedAsarIntegrityValidation is Enabled, the application refuses to start with a modified package and this option cannot be used.

  • If the application’s pages define a Content Security Policy, it must allow connections to https://unifiedcollector.pyze.com in connect-src. The SDK loads regardless of the policy, but it cannot send events to a domain the policy blocks. To check, search the extracted HTML files for Content-Security-Policy after step 1, or look for a Content Security Policy error naming unifiedcollector.pyze.com in the Developer Tools console. If the policy blocks the domain, this option cannot be used until the application’s supplier allows it.

Steps

  1. Extract the package into a folder named app:

    npx @electron/asar extract app.asar app
    
  2. Open app/package.json and note the value of main, for example main.js. This is the application’s startup script.

  3. Download the SDK from https://cdn.pyze.com/pyze.js and save it as app/pyze.js. Do not edit it.

  4. Create app/pyze-main.cjs with the following content. Do not change it; all settings go in the configuration file in the next step. Keep the .cjs file extension.

    // Loads the Pyze SDK into the application's pages, then starts the application.
    // Settings are read from pyze-config.json in the same folder.
    const { app } = require("electron");
    const fs = require("node:fs");
    const path = require("node:path");
    
    let config;
    try {
        config = JSON.parse(fs.readFileSync(path.join(__dirname, "pyze-config.json"), "utf8"));
    } catch (err) {
        throw new Error("Pyze: cannot read pyze-config.json: " + err.message);
    }
    for (const key of ["appKey", "originalMain", "whitelistedURLs"]) {
        if (!config[key]) throw new Error('Pyze: "' + key + '" is missing from pyze-config.json');
    }
    
    // Turns a pattern such as "https://*.example.com/*" into a regular expression.
    // A "*" matches anything except "/"; a "*" at the end matches the rest of the address.
    function toRegExp(pattern) {
        const parts = pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
        const last = parts.pop();
        return new RegExp("^" + parts.join("[^/]*") + (last === "" ? ".*" : "[^/]*" + last) + "$");
    }
    const whitelistedURLs = config.whitelistedURLs.map(toRegExp);
    
    const pyzeSdk = fs.readFileSync(path.join(__dirname, "pyze.js"), "utf8");
    
    // Does the same job as Pyze JavaScript in Option A, without downloading the SDK from the CDN.
    const pyzeSetup = `
    window.pyzeAppKey = ${JSON.stringify(config.appKey)};
    (function (w) {
      function stub(name, methods) {
        var api = w[name] = w[name] || { _q: [] };
        methods.forEach(function (method) {
          api[method] = function () {
            api._q.push([method].concat(Array.prototype.slice.call(arguments)));
          };
        });
      }
      stub("Pyze", ["initialize", "getTimerReference", "getAppInstanceId", "postWebAppVersion", "postPageView", "setUserOptOut", "deleteUser"]);
      stub("PyzeEvents", ["postCustomEvent", "startTimerForEvent", "postCustomEventWithAttributes", "postTimedEvent", "postTimedEventWithAttributes", "postExplicitActivation"]);
      stub("PyzeCuratedEvents", ["post"]);
      stub("PyzeIdentity", ["setUserProfile", "resetUserProfile", "updateUserProfile"]);
      w.Pyze.initialize(w.pyzeAppKey);
    })(window);
    `;
    
    app.on("web-contents-created", (event, contents) => {
        contents.on("dom-ready", () => {
            const url = contents.getURL();
            if (!whitelistedURLs.some((pattern) => pattern.test(url))) return;
            contents
                .executeJavaScript('typeof window.Pyze !== "undefined"')
                .then((alreadyLoaded) => {
                    if (alreadyLoaded) return;
                    return contents.executeJavaScript(pyzeSetup + pyzeSdk + "\n;void 0;");
                })
                .catch((err) => console.error("[pyze] could not load the SDK into", url, err));
        });
    });
    
    require(path.join(__dirname, config.originalMain));
    
  5. Create app/pyze-config.json with the following content:

    {
        "appKey": "YOUR_PYZE_APP_KEY",
        "originalMain": "main.js",
        "whitelistedURLs": ["file://*"]
    }
    
    • appKey: your Pyze App Key.
    • originalMain: the value you noted in step 2.
    • whitelistedURLs: address patterns of the pages Pyze is loaded into, as in Option A. Leave out other parties' pages, such as a sign-in page.
  6. In app/package.json, change the value of main to pyze-main.cjs. Do not change anything else in the file.

  7. Build the package again and replace the original app.asar with it:

    npx @electron/asar pack app app.asar
    

    If the application has an app.asar.unpacked folder next to app.asar, keep the same files unpacked with the --unpack or --unpack-dir option of @electron/asar.

  8. On macOS, changing the package invalidates the application’s code signature; sign the application again with your own certificate. On Windows, the executable file is not changed, so its signature is not affected.

  9. Start the application, check that it works as before, then verify the installation.

  10. Deploy the modified package with your software deployment tool.

Repeat these steps each time the application is updated. If the application updates itself automatically, turn that off, or the update replaces the modified package.


Verify Installation

  1. Start the application.
  2. Open Developer Tools: View → Toggle Developer Tools, or press Ctrl+Shift+I (Cmd+Option+I on macOS).
  3. In the Console tab, type Pyze. It should print an object, not undefined.
  4. In the Network tab, use the application for a moment. You should see requests to unifiedcollector.pyze.com returning status 202.

If the application has no Developer Tools menu, check that events arrive in your Pyze account.


Tracking Events and Users

Once Pyze is loaded, all Pyze Web SDK functions are available in the application’s pages. See the Web SDK guide for custom events, user profiles, dimensions and user privacy.


Troubleshooting

Symptom Likely cause Action
Pyze is undefined The page’s address does not match any pattern in whitelistedURLs, or (Option A) Pyze JavaScript is not in this window’s page Check the page address with location.href in the Developer Tools console and add a matching pattern; check that the page contains Pyze JavaScript
Console shows a Content Security Policy error for cdn.pyze.com, an inline script, or unifiedcollector.pyze.com The policy lacks one of the entries from Option A, Step 2 Add the missing entry
Pyze is defined but there are no requests to unifiedcollector.pyze.com The collector is blocked by a proxy or firewall, or the computer is offline Allow unifiedcollector.pyze.com on the network
Application does not start after its package was modified The application validates its package, or originalMain in pyze-config.json does not match the original main value Restore the original app.asar. Check originalMain, and check the fuses as described in Before you start
Application shows an error that starts with Pyze: cannot read pyze-config.json or Pyze: "..." is missing pyze-config.json is missing, is not valid JSON, or lacks one of its three settings Fix the file and build the package again
Application does not start after its package was modified, with an error that mentions ERR_REQUIRE_ESM The application uses ES modules on an older Electron version, which the startup script cannot load Restore the original app.asar and contact Pyze

Last modified 0001-01-01