Created
May 19, 2026 11:51
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?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