Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place

fix fallback behavior in hosting service

Changed files
+151 -2
hosting-service
+44 -1
hosting-service/src/lib/firehose.ts
··· 333 333 // Invalidate in-memory caches (includes metadata which stores settings) 334 334 invalidateSiteCache(did, rkey) 335 335 336 - // Update on-disk metadata with new settings 336 + // Check if site is already cached 337 + const cacheDir = `${CACHE_DIR}/${did}/${rkey}` 338 + const isCached = existsSync(cacheDir) 339 + 340 + if (!isCached) { 341 + this.log('Site not cached yet, checking if fs record exists', { did, rkey }) 342 + 343 + // If site exists on PDS, cache it (which will include the new settings) 344 + try { 345 + const siteRecord = await fetchSiteRecord(did, rkey) 346 + 347 + if (siteRecord) { 348 + this.log('Site record found, triggering full cache with settings', { did, rkey }) 349 + const pdsEndpoint = await getPdsForDid(did) 350 + 351 + if (pdsEndpoint) { 352 + // Mark as being cached 353 + markSiteAsBeingCached(did, rkey) 354 + 355 + try { 356 + await downloadAndCacheSite(did, rkey, siteRecord.record, pdsEndpoint, siteRecord.cid) 357 + this.log('Successfully cached site with new settings', { did, rkey }) 358 + } finally { 359 + unmarkSiteAsBeingCached(did, rkey) 360 + } 361 + } else { 362 + this.log('Could not resolve PDS for DID', { did }) 363 + } 364 + } else { 365 + this.log('No fs record found for site, skipping cache', { did, rkey }) 366 + } 367 + } catch (err) { 368 + this.log('Failed to cache site after settings change', { 369 + did, 370 + rkey, 371 + error: err instanceof Error ? err.message : String(err) 372 + }) 373 + } 374 + 375 + this.log('Successfully processed settings change (new cache)', { did, rkey }) 376 + return 377 + } 378 + 379 + // Site is already cached, just update the settings in metadata 337 380 try { 338 381 const { fetchSiteSettings, updateCacheMetadataSettings } = await import('./utils') 339 382 const settings = await fetchSiteSettings(did, rkey)
+23 -1
hosting-service/src/lib/utils.ts
··· 728 728 729 729 export async function getCachedSettings(did: string, rkey: string): Promise<WispSettings | null> { 730 730 const metadata = await getCacheMetadata(did, rkey); 731 - return metadata?.settings || null; 731 + 732 + // If metadata has settings, return them 733 + if (metadata?.settings) { 734 + return metadata.settings; 735 + } 736 + 737 + // If metadata exists but has no settings, try to fetch from PDS and update cache 738 + if (metadata) { 739 + console.log('[Cache] Metadata missing settings, fetching from PDS', { did, rkey }); 740 + try { 741 + const settings = await fetchSiteSettings(did, rkey); 742 + if (settings) { 743 + // Update the cached metadata with the fetched settings 744 + await updateCacheMetadataSettings(did, rkey, settings); 745 + console.log('[Cache] Updated metadata with fetched settings', { did, rkey }); 746 + return settings; 747 + } 748 + } catch (err) { 749 + console.error('[Cache] Failed to fetch/update settings', { did, rkey, err }); 750 + } 751 + } 752 + 753 + return null; 732 754 } 733 755 734 756 export async function updateCacheMetadataSettings(did: string, rkey: string, settings: WispSettings | null): Promise<void> {
+84
hosting-service/src/server.ts
··· 765 765 } 766 766 } 767 767 768 + // Directory listing fallback: if enabled, show root directory listing on 404 769 + if (settings?.directoryListing) { 770 + const rootPath = getCachedFilePath(did, rkey, ''); 771 + if (await fileExists(rootPath)) { 772 + const { stat, readdir } = await import('fs/promises'); 773 + try { 774 + const stats = await stat(rootPath); 775 + if (stats.isDirectory()) { 776 + const entries = await readdir(rootPath); 777 + // Filter out .meta files and metadata 778 + const visibleEntries = entries.filter(entry => 779 + !entry.endsWith('.meta') && entry !== '.metadata.json' 780 + ); 781 + 782 + // Check which entries are directories 783 + const entriesWithType = await Promise.all( 784 + visibleEntries.map(async (name) => { 785 + try { 786 + const entryPath = `${rootPath}/${name}`; 787 + const entryStats = await stat(entryPath); 788 + return { name, isDirectory: entryStats.isDirectory() }; 789 + } catch { 790 + return { name, isDirectory: false }; 791 + } 792 + }) 793 + ); 794 + 795 + const html = generateDirectoryListing('', entriesWithType); 796 + return new Response(html, { 797 + status: 404, 798 + headers: { 799 + 'Content-Type': 'text/html; charset=utf-8', 800 + 'Cache-Control': 'public, max-age=300', 801 + }, 802 + }); 803 + } 804 + } catch (err) { 805 + // If directory listing fails, fall through to 404 806 + } 807 + } 808 + } 809 + 768 810 // Default styled 404 page 769 811 const html = generate404Page(); 770 812 return new Response(html, { ··· 1160 1202 status: 404, 1161 1203 headers: response.headers, 1162 1204 }); 1205 + } 1206 + } 1207 + 1208 + // Directory listing fallback: if enabled, show root directory listing on 404 1209 + if (settings?.directoryListing) { 1210 + const rootPath = getCachedFilePath(did, rkey, ''); 1211 + if (await fileExists(rootPath)) { 1212 + const { stat, readdir } = await import('fs/promises'); 1213 + try { 1214 + const stats = await stat(rootPath); 1215 + if (stats.isDirectory()) { 1216 + const entries = await readdir(rootPath); 1217 + // Filter out .meta files and metadata 1218 + const visibleEntries = entries.filter(entry => 1219 + !entry.endsWith('.meta') && entry !== '.metadata.json' 1220 + ); 1221 + 1222 + // Check which entries are directories 1223 + const entriesWithType = await Promise.all( 1224 + visibleEntries.map(async (name) => { 1225 + try { 1226 + const entryPath = `${rootPath}/${name}`; 1227 + const entryStats = await stat(entryPath); 1228 + return { name, isDirectory: entryStats.isDirectory() }; 1229 + } catch { 1230 + return { name, isDirectory: false }; 1231 + } 1232 + }) 1233 + ); 1234 + 1235 + const html = generateDirectoryListing('', entriesWithType); 1236 + return new Response(html, { 1237 + status: 404, 1238 + headers: { 1239 + 'Content-Type': 'text/html; charset=utf-8', 1240 + 'Cache-Control': 'public, max-age=300', 1241 + }, 1242 + }); 1243 + } 1244 + } catch (err) { 1245 + // If directory listing fails, fall through to 404 1246 + } 1163 1247 } 1164 1248 } 1165 1249