Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<link rel="icon" type="image/svg+xml" href="/vite-logo.svg" />
<link rel="apple-touch-icon" href="/vite-logo.png" />
<link rel="apple-touch-icon" href="/pwa-icon.svg" />
<link rel="manifest" href="/manifest.webmanifest" />
<meta name="theme-color" content="#0f172a" />
<meta name="application-name" content="Echo Sphere" />

<title>Echo Sphere</title>
<meta name="description" content="Echo Sphere" />
<meta
name="description"
content="Install the Echo Sphere PWA to explore the demo offline and across devices."
/>
<!-- <meta name="author" content="" /> -->

<meta property="og:title" content="Echo Sphere" />
Expand Down
17 changes: 17 additions & 0 deletions app/public/manifest.webmanifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "Echo Sphere",
"short_name": "EchoSphere",
"start_url": "/",
"display": "standalone",
"background_color": "#0f172a",
"theme_color": "#0f172a",
"description": "Explore the Echo Sphere demo application even when you are offline.",
"icons": [
{
"src": "/pwa-icon.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any maskable"
}
]
}
15 changes: 15 additions & 0 deletions app/public/pwa-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
99 changes: 99 additions & 0 deletions app/public/sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
const CACHE_NAME = 'webframework-cache-v1'
const OFFLINE_URL = '/'

// Assets to cache on install
const STATIC_ASSETS = [
'/',
'/static/app/index.html',
'/static/app/manifest.webmanifest',
'/static/app/pwa-icon.svg',
'/static/app/vite-logo.svg',
]

self.addEventListener('install', (event) => {
event.waitUntil(
caches
.open(CACHE_NAME)
.then((cache) => {
// Cache static assets
return cache.addAll(STATIC_ASSETS.map((url) => new Request(url, { cache: 'reload' })))
})
.catch(() => {})
.then(() => self.skipWaiting())
)
})

self.addEventListener('activate', (event) => {
event.waitUntil(
caches
.keys()
.then((keys) =>
Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)))
)
.then(() => self.clients.claim())
)
})

self.addEventListener('fetch', (event) => {
const { request } = event

if (request.method !== 'GET' || request.url.startsWith('chrome-extension')) {
return
}

const requestURL = new URL(request.url)

if (requestURL.origin !== self.location.origin) {
return
}

event.respondWith(
caches.open(CACHE_NAME).then(async (cache) => {
try {
const response = await fetch(request)
if (
response &&
response.status === 200 &&
response.type === 'basic' &&
!request.url.includes('/__vite_ping') &&
!request.url.includes('/api/') // Don't cache API responses
) {
cache.put(request, response.clone()).catch(() => {})
}
return response
} catch (error) {
// Try to serve from cache
const cachedResponse = await cache.match(request)
if (cachedResponse) {
return cachedResponse
}

// For navigation requests, serve the offline page
if (request.mode === 'navigate') {
const offlineResponse =
(await cache.match('/static/app/index.html')) || (await cache.match(OFFLINE_URL))
if (offlineResponse) {
return offlineResponse
}
}

// For API requests, return a proper offline response
if (request.url.includes('/api/')) {
return new Response(
JSON.stringify({
error: 'Offline',
message: 'This feature is not available offline',
}),
{
status: 503,
statusText: 'Service Unavailable',
headers: { 'Content-Type': 'application/json' },
}
)
}

throw error
}
})
)
})
3 changes: 3 additions & 0 deletions app/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
import { registerServiceWorker } from './service-worker-registration.ts'

// biome-ignore lint/style/noNonNullAssertion: expect root element to exist
createRoot(document.getElementById('root')!).render(<App />)

registerServiceWorker()
36 changes: 36 additions & 0 deletions app/src/service-worker-registration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export function registerServiceWorker(): void {
if (!import.meta.env.PROD) {
return
}

if (!('serviceWorker' in navigator)) {
return
}

const register = () => {
// Use the correct path based on environment
const swPath = import.meta.env.PROD ? '/static/app/sw.js' : '/sw.js'

navigator.serviceWorker.register(swPath).catch((error) => {
console.error('Service worker registration failed:', error)
})
}

if (document.readyState === 'complete') {
register()
} else {
window.addEventListener('load', register, { once: true })
}
}

export function unregisterServiceWorker(): void {
if (!('serviceWorker' in navigator)) {
return
}

navigator.serviceWorker.ready
.then((registration) => registration.unregister())
.catch(() => {
// Swallow errors silently to avoid crashing the app when service worker cannot be unregistered.
})
}
9 changes: 6 additions & 3 deletions server/apps/website/templates/website/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
<link rel="icon" type="image/svg+xml" href="{% static 'vite-logo.svg' %}" />
<link rel="apple-touch-icon" href="{% static 'vite-logo.png' %}" />
{% else %}
<link rel="icon" type="image/svg+xml" href="/vite-logo.svg" />
<link rel="apple-touch-icon" href="/vite-logo.png" />
<link rel="icon" type="image/svg+xml" href="/static/app/vite-logo.svg" />
<link rel="apple-touch-icon" href="/static/app/pwa-icon.svg" />
<link rel="manifest" href="/static/app/manifest.webmanifest" />
<meta name="theme-color" content="#0f172a" />
<meta name="application-name" content="Echo Sphere" />
{% endif %}

<title>Echo Sphere</title>
Expand Down Expand Up @@ -94,4 +97,4 @@

</body>

</html>
</html>