Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save annuman97/4d60a5d5bba8a9d3dbf7eb3115b6dedd to your computer and use it in GitHub Desktop.

Select an option

Save annuman97/4d60a5d5bba8a9d3dbf7eb3115b6dedd to your computer and use it in GitHub Desktop.
A basic PWA snippet for FluentCommunity portal that adds a web app manifest, service worker registration, app-like home screen support, and a simple offline fallback. This is a temporary/basic PWA wrapper and does not include FluentCommunity push notification handling.
<?php
if (!function_exists('fcom_snippet_pwa_portal_start_url')) {
add_action('init', function () {
$fcomPwa = isset($_GET['fcom_pwa']) ? sanitize_key($_GET['fcom_pwa']) : '';
if ($fcomPwa === 'manifest') {
fcom_snippet_pwa_serve_manifest();
}
if ($fcomPwa === 'sw') {
fcom_snippet_pwa_serve_service_worker();
}
}, 0);
add_action('fluent_community/portal_head', 'fcom_snippet_pwa_print_head_tags');
add_action('fluent_community/headless/head', 'fcom_snippet_pwa_print_head_tags');
add_action('fluent_community/portal_footer', 'fcom_snippet_pwa_register_service_worker');
add_action('fluent_community/headless/before_js_loaded', 'fcom_snippet_pwa_register_service_worker');
function fcom_snippet_pwa_portal_start_url()
{
if (class_exists('\FluentCommunity\App\Services\Helper')) {
return \FluentCommunity\App\Services\Helper::baseUrl('/');
}
return home_url('/portal/');
}
function fcom_snippet_pwa_portal_scope_url()
{
return trailingslashit(preg_replace('/#.*/', '', fcom_snippet_pwa_portal_start_url()));
}
function fcom_snippet_pwa_theme_color()
{
return '#4A5FE0';
}
function fcom_snippet_pwa_print_head_tags()
{
static $printed = false;
if ($printed) {
return;
}
$printed = true;
?>
<link rel="manifest" href="<?php echo esc_url(home_url('/?fcom_pwa=manifest')); ?>">
<meta name="theme-color" content="<?php echo esc_attr(fcom_snippet_pwa_theme_color()); ?>">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-title" content="<?php echo esc_attr(get_bloginfo('name')); ?>">
<?php
}
function fcom_snippet_pwa_register_service_worker()
{
static $printed = false;
if ($printed) {
return;
}
$printed = true;
?>
<script>
(function () {
if (!('serviceWorker' in navigator)) return;
if (location.protocol !== 'https:' && location.hostname !== 'localhost' && location.hostname !== '127.0.0.1') return;
window.addEventListener('load', function () {
navigator.serviceWorker.register(
<?php echo wp_json_encode(home_url('/?fcom_pwa=sw')); ?>,
{ scope: <?php echo wp_json_encode(fcom_snippet_pwa_portal_scope_url()); ?> }
);
});
})();
</script>
<?php
}
function fcom_snippet_pwa_serve_manifest()
{
nocache_headers();
header('Content-Type: application/manifest+json; charset=utf-8');
$icons = [];
foreach ([192, 512] as $size) {
$icon = get_site_icon_url($size);
if ($icon) {
$icons[] = [
'src' => $icon,
'sizes' => $size . 'x' . $size,
'type' => 'image/png',
'purpose' => $size === 512 ? 'any maskable' : 'any',
];
}
}
echo wp_json_encode([
'id' => fcom_snippet_pwa_portal_scope_url(),
'name' => get_bloginfo('name') . ' Community',
'short_name' => get_bloginfo('name'),
'description' => get_bloginfo('description'),
'start_url' => fcom_snippet_pwa_portal_start_url(),
'scope' => fcom_snippet_pwa_portal_scope_url(),
'display' => 'standalone',
'background_color' => '#ffffff',
'theme_color' => fcom_snippet_pwa_theme_color(),
'icons' => $icons,
], JSON_UNESCAPED_SLASHES);
exit;
}
function fcom_snippet_pwa_serve_service_worker()
{
nocache_headers();
header('Content-Type: application/javascript; charset=utf-8');
header('Service-Worker-Allowed: /');
?>
const CACHE_NAME = 'fcom-pwa-v1';
const STATIC_DESTINATIONS = new Set(['style', 'script', 'image', 'font']);
const OFFLINE_HTML = '<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Offline</title><style>body{font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;margin:0;display:grid;min-height:100vh;place-items:center;background:#f6f7fb;color:#1f2937}.box{max-width:420px;padding:28px;text-align:center}h1{font-size:24px;margin:0 0 10px}p{margin:0;color:#4b5563;line-height:1.5}</style></head><body><div class="box"><h1>You are offline</h1><p>Reconnect to continue using the community.</p></div></body></html>';
self.addEventListener('install', event => {
self.skipWaiting();
});
self.addEventListener('activate', event => {
event.waitUntil((async () => {
const keys = await caches.keys();
await Promise.all(keys.filter(key => key !== CACHE_NAME).map(key => caches.delete(key)));
await self.clients.claim();
})());
});
self.addEventListener('fetch', event => {
const request = event.request;
if (request.method !== 'GET') return;
const url = new URL(request.url);
if (url.origin !== self.location.origin) return;
if (url.pathname.includes('/wp-admin/')) return;
if (url.pathname.includes('/wp-json/')) return;
if (url.pathname.endsWith('/wp-login.php')) return;
if (url.pathname.endsWith('/admin-ajax.php')) return;
if (request.mode === 'navigate') {
event.respondWith(
fetch(request).catch(() => new Response(OFFLINE_HTML, {
headers: { 'Content-Type': 'text/html; charset=utf-8' }
}))
);
return;
}
if (!STATIC_DESTINATIONS.has(request.destination)) return;
event.respondWith((async () => {
const cache = await caches.open(CACHE_NAME);
const cached = await cache.match(request);
const network = fetch(request).then(response => {
if (response && response.ok) {
cache.put(request, response.clone());
}
return response;
}).catch(() => cached);
return cached || network;
})());
});
<?php
exit;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment