Initial release v1.0.0

This commit is contained in:
Kevin O'Neill
2025-12-25 18:58:06 -06:00
commit 021aec7a63
439 changed files with 116588 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1012 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+70
View File
@@ -0,0 +1,70 @@
{
"name": "Lidify",
"short_name": "Lidify",
"description": "Self-hosted music streaming with Lidarr integration",
"start_url": "/",
"display": "standalone",
"display_override": ["window-controls-overlay", "standalone"],
"orientation": "any",
"theme_color": "#000000",
"background_color": "#000000",
"categories": ["music", "entertainment"],
"icons": [
{
"src": "assets/icons/icon-48.webp",
"type": "image/webp",
"sizes": "48x48",
"purpose": "any"
},
{
"src": "assets/icons/icon-72.webp",
"type": "image/webp",
"sizes": "72x72",
"purpose": "any"
},
{
"src": "assets/icons/icon-96.webp",
"type": "image/webp",
"sizes": "96x96",
"purpose": "any"
},
{
"src": "assets/icons/icon-128.webp",
"type": "image/webp",
"sizes": "128x128",
"purpose": "any"
},
{
"src": "assets/icons/icon-192.webp",
"type": "image/webp",
"sizes": "192x192",
"purpose": "any maskable"
},
{
"src": "assets/icons/icon-256.webp",
"type": "image/webp",
"sizes": "256x256",
"purpose": "any"
},
{
"src": "assets/icons/icon-512.webp",
"type": "image/webp",
"sizes": "512x512",
"purpose": "any maskable"
}
],
"shortcuts": [
{
"name": "Search",
"short_name": "Search",
"url": "/search",
"icons": [{ "src": "assets/icons/icon-96.webp", "sizes": "96x96" }]
},
{
"name": "Library",
"short_name": "Library",
"url": "/library",
"icons": [{ "src": "assets/icons/icon-96.webp", "sizes": "96x96" }]
}
]
}
+90
View File
@@ -0,0 +1,90 @@
// Lidify Service Worker
const CACHE_NAME = 'lidify-v1';
// Assets to cache on install (app shell)
const PRECACHE_ASSETS = [
'/',
'/manifest.webmanifest',
'/assets/images/LIDIFY.webp',
];
// Install event - cache app shell
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(PRECACHE_ASSETS);
})
);
// Take control immediately
self.skipWaiting();
});
// Activate event - clean up old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((name) => name !== CACHE_NAME)
.map((name) => caches.delete(name))
);
})
);
// Take control of all clients immediately
self.clients.claim();
});
// Fetch event - network first, fallback to cache
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
// Skip non-GET requests
if (request.method !== 'GET') return;
// Skip non-http(s) URLs (chrome-extension://, etc.)
if (!url.protocol.startsWith('http')) return;
// Skip API requests - always go to network
if (url.pathname.startsWith('/api/')) return;
// Skip streaming endpoints
if (url.pathname.includes('/stream')) return;
// Skip Next.js image optimization endpoint
if (url.pathname.startsWith('/_next/image')) return;
// For everything else, try network first, then cache
event.respondWith(
fetch(request)
.then((response) => {
// Clone the response before caching
const responseClone = response.clone();
// Cache successful responses
if (response.status === 200) {
caches.open(CACHE_NAME).then((cache) => {
cache.put(request, responseClone);
});
}
return response;
})
.catch(() => {
// Network failed, try cache
return caches.match(request).then((cachedResponse) => {
if (cachedResponse) {
return cachedResponse;
}
// Return a fallback for navigation requests
if (request.mode === 'navigate') {
return caches.match('/');
}
return new Response('Offline', { status: 503 });
});
})
);
});