Connect PWAs and IWAs with Chrome Extensions

Demián Renzulli
Demián Renzulli

As a web developer, it's best practice to design your applications using the lowest-trust security model possible, such as a Progressive Web App (PWA). This approach maximizes your reach, minimizes the security overhead you have to manage, and offers the greatest flexibility for both developers and users. However, because the web is designed to be safe by default, its conservative security model naturally restricts access to the operating system and certain powerful device APIs.

Isolated Web Apps (IWAs) solve this by providing an isolated, bundled, versioned, signed, and highly trusted application model built on top of the web platform. Yet, before making the leap to an IWA, it's worth considering a more gradual step: connecting your PWA with a Chrome Extension. Available in managed ChromeOS environments—such as managed user sessions, Managed Guest Sessions (MGS), or Kiosk mode—this technique lets your app use lower-level extension APIs through secure message passing. The following diagram illustrates this progressive approach: starting with a standard web application, adding the capabilities to become an installable PWA, and finally exploring the PWA and Chrome Extension path to unlock additional APIs.

image
The progressive enhancement path. By pairing an installable PWA with a companion Chrome Extension, developers can bridge the gap between the web's secure environment and lower-level OS and device features.

If your application requires advanced capabilities that remain unavailable even with Chrome Extension APIs—such as Controlled Frame or the Direct Sockets API—then migrating to an Isolated Web App (IWA) is your best path forward. However, while IWAs unlock powerful new web features, you might still need specific device-level APIs that are exclusive to Chrome Extensions, such as chrome.runtime.restart() for rebooting a ChromeOS device in Kiosk mode. Fortunately, you can connect an IWA to a Chrome Extension using the exact same approach as a PWA. This technique is covered in the following steps.

Step by step implementation

Deploy the Companion Extension

Extensions are deployed through the Chrome Admin Console. Depending on your target environment, you will configure this in the corresponding section (for example, navigate to Devices > Chrome > Apps & Extensions > Kiosks for Kiosk mode, or the respective tabs for Users & Browsers or Managed Guest Sessions). You can either self-host the extension at a publicly accessible link or host it directly in the Chrome Web Store. For more detailed instructions on managing extensions refer to the official documentation.

Implement message passing

Extension setup

To receive and respond to messages from your web app, expose a background script that listens for messages to arrive from the client (your web app) and then proxy those requests to a corresponding API call. In the following example, a request is proxied to restart the ChromeOS device when the web app sends a custom message object that contains a methodName of callRestart.

Background.js

// message handler - extension code
chrome.runtime.onMessageExternal.addListener(function (request, sender, sendResponse) {
  if (request.methodName == 'callRestart') {
    chrome.runtime.restart();
  }
});

The manifest for the extension can be configured to allow external function calls to the extension using the externally_connectable⁠⁠ key which specifies what sites and extensions are allowed to call methods in the extension. More information about Chrome extensions and manifest v3 can be found in the official documentation⁠⁠.

If you are connecting from a Progressive Web App (PWA), you will list the standard HTTPS domain where your app is hosted under the matches array. Here is an example of a manifest configured for a PWA running in Kiosk mode:

Manifest.json

{
  "manifest_version": 3,
  "name": "Restart your kiosk app",
  "version": "1.0",
  "description": "This restarts your ChromeOS device.",
  "background": {
    "service_worker": "background.js"
  },
  "externally_connectable": {
    "accepts_tls_channel_id": false,
    "matches": [
      "*://developer.chrome.com/*"
    ]
  }
}

If you are connecting from an Isolated Web App (IWA), the mechanism is exactly the same, but the URL scheme changes. Because IWAs are securely packaged and don't run on standard web servers, they use their own protocol. You must add the IWA's origin using the isolated-app:// scheme.

Manifest.json

{
  "manifest_version": 3,
  "name": "IWA Companion Extension",
  "version": "1.1",
  "description": "Companion extension for the IWA",
  "background": {
    "service_worker": "/scripts/background.js"
  },
  "externally_connectable": {
    "matches": [
      "isolated-app://*/*"
    ]
  }
}

This is the minimum amount of code required in an extension to listen for messages from a PWA or IWA.

PWA and IWA setup

To call the extension from a web app, you need to know its static extension ID. This ID can be found on the chrome://extensions page, shown when you install your Chrome extension, or from the Chrome Web Store after the extension has been uploaded. This lets your web app specify the exact extension to communicate with. After that, call chrome.runtime.sendMessage⁠⁠ and pass in the extension ID with a message to send to the extension.

const STATIC_EXTENSION_ID = 'abcdefghijklmnopqrstuvwxyz';
// found from chrome extensions page of chrome web store.
const callExtensionAPI = function (method) {
  chrome.runtime.sendMessage(STATIC_EXTENSION_ID, {
    methodName: method,
  });
};
callExtensionAPI('callRestart');

For more information on connecting web apps to extensions for message passing, refer to the Extensions documentation⁠⁠.

Demo

To see this implementation in action, explore the IWA Kitchen Sink repository. This project serves as a comprehensive playground for various IWA capabilities, featuring demos for high-trust APIs like Direct Sockets and Controlled Frame. It also provides a complete, working example of the IWA-to-Chrome-Extension connection. The repository includes a sample companion extension and a dedicated web interface that demonstrates how to use secure message passing to trigger extension-exclusive methods. For example, you can test fetching the user's profile information with the chrome.identity.getProfileUserInfo() API directly from the Isolated Web App.

Conclusion

Connecting your web applications to a Chrome Extension offers a secure, progressive path to unlocking native-like device capabilities. As you design your app's architecture, keep these key takeaways in mind:

  1. Start with the Web: Default to a PWA for the best reach and lowest security overhead.
  2. Bridge the gap with Extensions: For deeply integrated, OS-level features (like device rebooting in Kiosk mode), deploy a companion Chrome Extension and connect it to your application using secure message passing.
  3. Upgrade to IWAs only if needed: Use Isolated Web Apps when you require high-trust APIs like Direct Sockets, Controlled Frame or any of the other IWA-only APIs.