chunk-046-pwa.js

I’ll build this as a strong standalone PWA helper chunk for RX Theme: install prompt, offline status, service-worker registration, push/share helpers, cache warming hooks, and safe WordPress-friendly guards.

My dear friend, create this file:

/assets/static-js/static-chunks-js/chunk-046-pwa.js

Then paste this full advanced PWA code:

/*!
 * RX Theme - Advanced PWA Chunk
 * File: /assets/static-js/static-chunks-js/chunk-046-pwa.js
 * Purpose: PWA install prompt, service worker registration, offline/online UI,
 * app update detection, cache warming, Web Share API, notification helper,
 * standalone mode detection, and safe WordPress frontend integration.
 */

(function () {
  'use strict';

  /**
   * RX PWA CONFIG
   * You can override these values before loading this file:
   *
   * window.RX_PWA_CONFIG = {
   *   serviceWorkerPath: '/service-worker.js',
   *   scope: '/',
   *   enableInstallPrompt: true,
   *   enableOfflineBar: true,
   *   enableUpdateToast: true,
   *   enableShareButton: true,
   *   enableNotificationHelper: false,
   *   enableCacheWarmup: true,
   *   cacheWarmupUrls: ['/', '/offline/'],
   * };
   */

  var defaultConfig = {
    serviceWorkerPath: '/service-worker.js',
    scope: '/',
    enableInstallPrompt: true,
    enableOfflineBar: true,
    enableUpdateToast: true,
    enableShareButton: true,
    enableNotificationHelper: false,
    enableCacheWarmup: true,
    cacheWarmupUrls: ['/', '/offline/'],
    installPromptSelector: '[data-rx-pwa-install]',
    shareButtonSelector: '[data-rx-pwa-share]',
    notificationButtonSelector: '[data-rx-pwa-notification]',
    updateCheckInterval: 60 * 60 * 1000,
    debug: false
  };

  var config = extend(defaultConfig, window.RX_PWA_CONFIG || {});
  var deferredInstallPrompt = null;
  var serviceWorkerRegistration = null;
  var hasNewServiceWorker = false;
  var updateToastEl = null;
  var offlineBarEl = null;

  var RX_PWA = {
    config: config,
    isSupported: isPWASupported(),
    isStandalone: isStandaloneMode(),
    serviceWorkerRegistration: null,
    deferredInstallPrompt: null,
    registerServiceWorker: registerServiceWorker,
    unregisterServiceWorker: unregisterServiceWorker,
    checkForUpdates: checkForUpdates,
    showInstallPrompt: showInstallPrompt,
    requestNotificationPermission: requestNotificationPermission,
    showLocalNotification: showLocalNotification,
    shareCurrentPage: shareCurrentPage,
    warmupCache: warmupCache,
    clearRuntimeCaches: clearRuntimeCaches,
    getStatus: getStatus
  };

  window.RX_PWA = RX_PWA;

  ready(function () {
    addRootClasses();
    bindNetworkEvents();

    if (config.enableOfflineBar) {
      createOfflineBar();
      updateOfflineBar();
    }

    if (config.enableInstallPrompt) {
      bindInstallPrompt();
      bindInstallButtons();
    }

    if (config.enableShareButton) {
      bindShareButtons();
    }

    if (config.enableNotificationHelper) {
      bindNotificationButtons();
    }

    registerServiceWorker();

    if (config.enableCacheWarmup) {
      warmupCache(config.cacheWarmupUrls);
    }
  });

  /**
   * Utilities
   */

  function log() {
    if (!config.debug || !window.console) return;
    console.log.apply(console, ['[RX PWA]'].concat(Array.prototype.slice.call(arguments)));
  }

  function warn() {
    if (!config.debug || !window.console) return;
    console.warn.apply(console, ['[RX PWA]'].concat(Array.prototype.slice.call(arguments)));
  }

  function extend(target) {
    var sources = Array.prototype.slice.call(arguments, 1);

    sources.forEach(function (source) {
      if (!source) return;

      Object.keys(source).forEach(function (key) {
        target[key] = source[key];
      });
    });

    return target;
  }

  function ready(callback) {
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', callback, { once: true });
    } else {
      callback();
    }
  }

  function createEl(tag, className, text) {
    var el = document.createElement(tag);

    if (className) {
      el.className = className;
    }

    if (typeof text === 'string') {
      el.textContent = text;
    }

    return el;
  }

  function safeJsonParse(value, fallback) {
    try {
      return JSON.parse(value);
    } catch (error) {
      return fallback;
    }
  }

  function emit(name, detail) {
    var event;

    try {
      event = new CustomEvent(name, {
        detail: detail || {},
        bubbles: true
      });
    } catch (error) {
      event = document.createEvent('CustomEvent');
      event.initCustomEvent(name, true, false, detail || {});
    }

    document.dispatchEvent(event);
  }

  function isPWASupported() {
    return 'serviceWorker' in navigator;
  }

  function isStandaloneMode() {
    return (
      window.matchMedia('(display-mode: standalone)').matches ||
      window.navigator.standalone === true ||
      document.referrer.indexOf('android-app://') === 0
    );
  }

  function addRootClasses() {
    var html = document.documentElement;

    html.classList.add('rx-pwa-js');

    if (isPWASupported()) {
      html.classList.add('rx-pwa-supported');
    } else {
      html.classList.add('rx-pwa-not-supported');
    }

    if (isStandaloneMode()) {
      html.classList.add('rx-pwa-standalone');
    } else {
      html.classList.add('rx-pwa-browser');
    }

    if (navigator.onLine) {
      html.classList.add('rx-pwa-online');
      html.classList.remove('rx-pwa-offline');
    } else {
      html.classList.add('rx-pwa-offline');
      html.classList.remove('rx-pwa-online');
    }
  }

  /**
   * Service Worker
   */

  function registerServiceWorker() {
    if (!isPWASupported()) {
      warn('Service Worker not supported.');
      emit('rx:pwa:unsupported');
      return Promise.resolve(null);
    }

    return navigator.serviceWorker
      .register(config.serviceWorkerPath, {
        scope: config.scope
      })
      .then(function (registration) {
        serviceWorkerRegistration = registration;
        RX_PWA.serviceWorkerRegistration = registration;

        log('Service Worker registered:', registration.scope);
        emit('rx:pwa:registered', { registration: registration });

        listenForServiceWorkerUpdates(registration);
        listenForServiceWorkerMessages();

        setInterval(function () {
          checkForUpdates();
        }, config.updateCheckInterval);

        return registration;
      })
      .catch(function (error) {
        warn('Service Worker registration failed:', error);
        emit('rx:pwa:registration-failed', { error: error });
        return null;
      });
  }

  function unregisterServiceWorker() {
    if (!isPWASupported()) {
      return Promise.resolve(false);
    }

    return navigator.serviceWorker.getRegistrations().then(function (registrations) {
      var jobs = registrations.map(function (registration) {
        return registration.unregister();
      });

      return Promise.all(jobs).then(function () {
        emit('rx:pwa:unregistered');
        return true;
      });
    });
  }

  function listenForServiceWorkerUpdates(registration) {
    if (!registration) return;

    registration.addEventListener('updatefound', function () {
      var newWorker = registration.installing;

      if (!newWorker) return;

      log('New service worker installing.');

      newWorker.addEventListener('statechange', function () {
        log('New service worker state:', newWorker.state);

        if (
          newWorker.state === 'installed' &&
          navigator.serviceWorker.controller
        ) {
          hasNewServiceWorker = true;
          emit('rx:pwa:update-available', { registration: registration });

          if (config.enableUpdateToast) {
            showUpdateToast();
          }
        }
      });
    });
  }

  function listenForServiceWorkerMessages() {
    if (!navigator.serviceWorker) return;

    navigator.serviceWorker.addEventListener('message', function (event) {
      var data = event.data || {};

      if (!data.type) return;

      if (data.type === 'RX_SW_CACHE_UPDATED') {
        emit('rx:pwa:cache-updated', data);
      }

      if (data.type === 'RX_SW_OFFLINE_FALLBACK') {
        emit('rx:pwa:offline-fallback', data);
      }

      if (data.type === 'RX_SW_VERSION') {
        emit('rx:pwa:version', data);
      }
    });

    navigator.serviceWorker.addEventListener('controllerchange', function () {
      if (hasNewServiceWorker) {
        window.location.reload();
      }
    });
  }

  function checkForUpdates() {
    if (!serviceWorkerRegistration || !serviceWorkerRegistration.update) {
      return Promise.resolve(false);
    }

    return serviceWorkerRegistration
      .update()
      .then(function () {
        emit('rx:pwa:update-checked');
        return true;
      })
      .catch(function (error) {
        warn('Update check failed:', error);
        return false;
      });
  }

  function activateWaitingServiceWorker() {
    if (!serviceWorkerRegistration || !serviceWorkerRegistration.waiting) {
      window.location.reload();
      return;
    }

    serviceWorkerRegistration.waiting.postMessage({
      type: 'RX_SW_SKIP_WAITING'
    });
  }

  /**
   * Update Toast
   */

  function showUpdateToast() {
    if (updateToastEl) return;

    updateToastEl = createEl('div', 'rx-pwa-update-toast');
    updateToastEl.setAttribute('role', 'status');
    updateToastEl.setAttribute('aria-live', 'polite');

    var message = createEl(
      'span',
      'rx-pwa-update-toast__message',
      'A new version is available.'
    );

    var button = createEl(
      'button',
      'rx-pwa-update-toast__button',
      'Update now'
    );

    var close = createEl(
      'button',
      'rx-pwa-update-toast__close',
      '×'
    );

    button.type = 'button';
    close.type = 'button';
    close.setAttribute('aria-label', 'Close update message');

    button.addEventListener('click', function () {
      activateWaitingServiceWorker();
    });

    close.addEventListener('click', function () {
      hideUpdateToast();
    });

    updateToastEl.appendChild(message);
    updateToastEl.appendChild(button);
    updateToastEl.appendChild(close);
    document.body.appendChild(updateToastEl);

    setTimeout(function () {
      updateToastEl.classList.add('is-visible');
    }, 50);
  }

  function hideUpdateToast() {
    if (!updateToastEl) return;

    updateToastEl.classList.remove('is-visible');

    setTimeout(function () {
      if (updateToastEl && updateToastEl.parentNode) {
        updateToastEl.parentNode.removeChild(updateToastEl);
      }

      updateToastEl = null;
    }, 250);
  }

  /**
   * Offline / Online Bar
   */

  function bindNetworkEvents() {
    window.addEventListener('online', function () {
      document.documentElement.classList.add('rx-pwa-online');
      document.documentElement.classList.remove('rx-pwa-offline');
      updateOfflineBar();
      emit('rx:pwa:online');
    });

    window.addEventListener('offline', function () {
      document.documentElement.classList.add('rx-pwa-offline');
      document.documentElement.classList.remove('rx-pwa-online');
      updateOfflineBar();
      emit('rx:pwa:offline');
    });
  }

  function createOfflineBar() {
    if (offlineBarEl) return;

    offlineBarEl = createEl('div', 'rx-pwa-offline-bar');
    offlineBarEl.setAttribute('role', 'status');
    offlineBarEl.setAttribute('aria-live', 'polite');
    offlineBarEl.textContent = 'You are offline. Some pages may still work from cache.';

    document.body.appendChild(offlineBarEl);
  }

  function updateOfflineBar() {
    if (!offlineBarEl) return;

    if (navigator.onLine) {
      offlineBarEl.classList.remove('is-visible');
    } else {
      offlineBarEl.classList.add('is-visible');
    }
  }

  /**
   * Install Prompt
   */

  function bindInstallPrompt() {
    window.addEventListener('beforeinstallprompt', function (event) {
      event.preventDefault();

      deferredInstallPrompt = event;
      RX_PWA.deferredInstallPrompt = event;

      document.documentElement.classList.add('rx-pwa-installable');
      emit('rx:pwa:installable', { event: event });

      showInstallButtons();
    });

    window.addEventListener('appinstalled', function () {
      deferredInstallPrompt = null;
      RX_PWA.deferredInstallPrompt = null;

      document.documentElement.classList.remove('rx-pwa-installable');
      document.documentElement.classList.add('rx-pwa-installed');

      hideInstallButtons();
      emit('rx:pwa:installed');
    });
  }

  function bindInstallButtons() {
    var buttons = document.querySelectorAll(config.installPromptSelector);

    if (!buttons.length) return;

    Array.prototype.forEach.call(buttons, function (button) {
      button.hidden = true;

      button.addEventListener('click', function (event) {
        event.preventDefault();
        showInstallPrompt();
      });
    });
  }

  function showInstallButtons() {
    var buttons = document.querySelectorAll(config.installPromptSelector);

    Array.prototype.forEach.call(buttons, function (button) {
      button.hidden = false;
      button.classList.add('is-visible');
    });
  }

  function hideInstallButtons() {
    var buttons = document.querySelectorAll(config.installPromptSelector);

    Array.prototype.forEach.call(buttons, function (button) {
      button.hidden = true;
      button.classList.remove('is-visible');
    });
  }

  function showInstallPrompt() {
    if (!deferredInstallPrompt) {
      emit('rx:pwa:install-unavailable');
      return Promise.resolve({
        available: false,
        outcome: 'unavailable'
      });
    }

    deferredInstallPrompt.prompt();

    return deferredInstallPrompt.userChoice
      .then(function (choiceResult) {
        var outcome = choiceResult.outcome;

        emit('rx:pwa:install-choice', {
          outcome: outcome
        });

        deferredInstallPrompt = null;
        RX_PWA.deferredInstallPrompt = null;
        hideInstallButtons();

        return {
          available: true,
          outcome: outcome
        };
      })
      .catch(function (error) {
        warn('Install prompt failed:', error);

        return {
          available: false,
          outcome: 'error',
          error: error
        };
      });
  }

  /**
   * Web Share API
   */

  function bindShareButtons() {
    var buttons = document.querySelectorAll(config.shareButtonSelector);

    if (!buttons.length) return;

    Array.prototype.forEach.call(buttons, function (button) {
      if (!navigator.share) {
        button.hidden = true;
        return;
      }

      button.addEventListener('click', function (event) {
        event.preventDefault();

        var customData = safeJsonParse(
          button.getAttribute('data-rx-pwa-share-data') || '{}',
          {}
        );

        shareCurrentPage(customData);
      });
    });
  }

  function shareCurrentPage(customData) {
    if (!navigator.share) {
      emit('rx:pwa:share-unavailable');
      return Promise.resolve(false);
    }

    var data = extend(
      {
        title: document.title || 'RX Theme',
        text: getMetaContent('description') || '',
        url: window.location.href
      },
      customData || {}
    );

    return navigator.share(data)
      .then(function () {
        emit('rx:pwa:shared', data);
        return true;
      })
      .catch(function (error) {
        emit('rx:pwa:share-cancelled', { error: error });
        return false;
      });
  }

  function getMetaContent(name) {
    var meta =
      document.querySelector('meta[name="' + name + '"]') ||
      document.querySelector('meta[property="og:' + name + '"]');

    return meta ? meta.getAttribute('content') : '';
  }

  /**
   * Notification Helper
   */

  function bindNotificationButtons() {
    var buttons = document.querySelectorAll(config.notificationButtonSelector);

    if (!buttons.length) return;

    Array.prototype.forEach.call(buttons, function (button) {
      if (!('Notification' in window)) {
        button.hidden = true;
        return;
      }

      button.addEventListener('click', function (event) {
        event.preventDefault();

        requestNotificationPermission().then(function (permission) {
          if (permission === 'granted') {
            button.classList.add('is-granted');
          }
        });
      });
    });
  }

  function requestNotificationPermission() {
    if (!('Notification' in window)) {
      emit('rx:pwa:notification-unsupported');
      return Promise.resolve('unsupported');
    }

    if (Notification.permission === 'granted') {
      emit('rx:pwa:notification-granted');
      return Promise.resolve('granted');
    }

    if (Notification.permission === 'denied') {
      emit('rx:pwa:notification-denied');
      return Promise.resolve('denied');
    }

    return Notification.requestPermission().then(function (permission) {
      emit('rx:pwa:notification-permission', {
        permission: permission
      });

      return permission;
    });
  }

  function showLocalNotification(title, options) {
    if (!('Notification' in window)) {
      return Promise.resolve(false);
    }

    return requestNotificationPermission().then(function (permission) {
      if (permission !== 'granted') {
        return false;
      }

      var notificationOptions = extend(
        {
          body: '',
          icon: '/icon-192x192.png',
          badge: '/badge-72x72.png',
          tag: 'rx-theme-notification',
          renotify: false
        },
        options || {}
      );

      if (serviceWorkerRegistration && serviceWorkerRegistration.showNotification) {
        return serviceWorkerRegistration
          .showNotification(title || 'RX Theme', notificationOptions)
          .then(function () {
            return true;
          });
      }

      new Notification(title || 'RX Theme', notificationOptions);
      return true;
    });
  }

  /**
   * Cache Warmup
   */

  function warmupCache(urls) {
    if (!navigator.serviceWorker || !navigator.serviceWorker.controller) {
      return Promise.resolve(false);
    }

    if (!Array.isArray(urls) || !urls.length) {
      return Promise.resolve(false);
    }

    navigator.serviceWorker.controller.postMessage({
      type: 'RX_SW_WARMUP_CACHE',
      urls: urls
    });

    emit('rx:pwa:cache-warmup-requested', {
      urls: urls
    });

    return Promise.resolve(true);
  }

  function clearRuntimeCaches() {
    if (!navigator.serviceWorker || !navigator.serviceWorker.controller) {
      return Promise.resolve(false);
    }

    navigator.serviceWorker.controller.postMessage({
      type: 'RX_SW_CLEAR_RUNTIME_CACHE'
    });

    emit('rx:pwa:cache-clear-requested');

    return Promise.resolve(true);
  }

  /**
   * PWA Status
   */

  function getStatus() {
    return {
      supported: isPWASupported(),
      standalone: isStandaloneMode(),
      online: navigator.onLine,
      installable: !!deferredInstallPrompt,
      hasNewServiceWorker: hasNewServiceWorker,
      notification:
        'Notification' in window ? Notification.permission : 'unsupported',
      serviceWorkerRegistered: !!serviceWorkerRegistration,
      scope: serviceWorkerRegistration ? serviceWorkerRegistration.scope : null
    };
  }
})();

Also add this CSS in your main CSS file so the offline bar and update toast look clean:

/* RX Theme PWA UI */

.rx-pwa-offline-bar {
  position: fixed;
  right: 16px;
  bottom: 16px;
  z-index: 99999;
  max-width: 360px;
  padding: 12px 16px;
  border-radius: 12px;
  background: #1f2937;
  color: #ffffff;
  font-size: 14px;
  line-height: 1.5;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.25);
  transform: translateY(120%);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s ease, transform 0.25s ease, visibility 0.25s ease;
}

.rx-pwa-offline-bar.is-visible {
  transform: translateY(0);
  opacity: 1;
  visibility: visible;
}

.rx-pwa-update-toast {
  position: fixed;
  left: 16px;
  bottom: 16px;
  z-index: 99999;
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: 420px;
  padding: 12px;
  border-radius: 14px;
  background: #ffffff;
  color: #111827;
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.22);
  transform: translateY(120%);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s ease, transform 0.25s ease, visibility 0.25s ease;
}

.rx-pwa-update-toast.is-visible {
  transform: translateY(0);
  opacity: 1;
  visibility: visible;
}

.rx-pwa-update-toast__message {
  flex: 1;
  font-size: 14px;
  line-height: 1.4;
}

.rx-pwa-update-toast__button,
.rx-pwa-update-toast__close {
  border: 0;
  cursor: pointer;
  border-radius: 999px;
  font-size: 14px;
}

.rx-pwa-update-toast__button {
  padding: 8px 12px;
  background: #2563eb;
  color: #ffffff;
  font-weight: 600;
}

.rx-pwa-update-toast__close {
  width: 30px;
  height: 30px;
  background: #f3f4f6;
  color: #111827;
  font-size: 20px;
  line-height: 1;
}

[data-rx-pwa-install][hidden],
[data-rx-pwa-share][hidden],
[data-rx-pwa-notification][hidden] {
  display: none !important;
}

@media (max-width: 600px) {
  .rx-pwa-offline-bar,
  .rx-pwa-update-toast {
    right: 12px;
    left: 12px;
    bottom: 12px;
    max-width: none;
  }
}

Use buttons like this anywhere in your theme:

<button type="button" data-rx-pwa-install>
  Install RX App
</button>

<button type="button" data-rx-pwa-share>
  Share This Page
</button>

<button type="button" data-rx-pwa-notification>
  Enable Notifications
</button>

Load it in your theme like this:

wp_enqueue_script(
    'rx-theme-pwa',
    get_template_directory_uri() . '/assets/static-js/static-chunks-js/chunk-046-pwa.js',
    array(),
    wp_get_theme()->get('Version'),
    true
);

Important: this file works best when you also have a real /service-worker.js and /manifest.webmanifest. Without those two, install and offline features will be limited.

Leave a Reply

Your email address will not be published. Required fields are marked *