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.