Follow-up fix for deployed PWA service-worker path and type-fetch 404 noise (#1583)

* Fix ManifoldCAD PWA paths and manifest metadata

* fix(editor): handle service worker registration failures and stabilize PWA path resolution

* fix(editor): handle service worker registration failures and stabilize PWA path resolution

* style(editor): align editor.js with clang-format

* fix(editor): clear type fetch indicator after resolution

* fix(editor): improve type indicator visibility and harden service worker

- Declare autoTypings with let before AutoTypings.create() to avoid
  TDZ issues when callbacks fire during creation
- Enforce a minimum 350ms display window for the type-fetch indicator
  so it is visible even when resolution completes very quickly
- Track resolveEventUntil timestamp so a ResolveNewImports event alone
  is enough to keep the indicator on even if isResolving clears early
- Use console.log (not console.debug) for indicator ON/OFF transitions
  so they appear without enabling Verbose in DevTools

service-worker.js:
- Guard fetch handler to only intercept GET requests so POST/PUT etc.
  pass through without error
- Wrap network fetch in try/catch; fall back to cached response on
  network failure instead of propagating an uncaught rejection
- Add skipWaiting() on install and clients.claim() on activate for
  immediate worker takeover on update

* style(editor): apply clang-format to editor.js and service-worker.js

* refactor(editor): simplify type indicator for review split
This commit is contained in:
Israr
2026-03-15 22:41:51 +13:00
committed by GitHub
parent 5d21276946
commit 4a56b0f9f2
2 changed files with 72 additions and 26 deletions
+31 -7
View File
@@ -17,7 +17,7 @@
import esbuildWasmUrl from 'esbuild-wasm/esbuild.wasm?url';
import ManifoldWorker from 'manifold-3d/lib/worker.bundled.js?worker';
import manifoldWasmUrl from 'manifold-3d/manifold.wasm?url';
import {AutoTypings, LocalStorageCache} from 'monaco-editor-auto-typings';
import {AutoTypings, JsDelivrSourceResolver, LocalStorageCache} from 'monaco-editor-auto-typings';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.main';
// '?worker' is vite convention to load a module as a web worker.
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
@@ -28,9 +28,13 @@ const CODE_START = '<code>';
const exampleFunctions = self.examples;
if (navigator.serviceWorker) {
// Resolve against the current module URL so registration works on subpaths.
const serviceWorkerUrl = new URL('./service-worker.js', import.meta.url);
navigator.serviceWorker.register(serviceWorkerUrl, {scope: './'});
// Resolve against the current page URL so production asset paths don't
// redirect registration into /assets.
const serviceWorkerUrl = new URL('./service-worker.js', window.location.href);
navigator.serviceWorker.register(serviceWorkerUrl, {scope: './'})
.catch(error => {
console.error('Service worker registration failed:', error);
});
}
let editor = undefined;
@@ -318,15 +322,20 @@ async function createEditor() {
});
// Make sure `manifold-3d/manifoldCAD` types are available for import.
const manifoldCADTypesUrl =
new URL('./manifoldCAD.d.ts', window.location.href);
const manifoldCADGlobalsTypesUrl =
new URL('./manifoldCADGlobals.d.ts', window.location.href);
monaco.languages.typescript.typescriptDefaults.addExtraLib(
await (await fetch('/manifoldCAD.d.ts')).text(),
await (await fetch(manifoldCADTypesUrl)).text(),
'inmemory://model/node_modules/manifold-3d/manifoldCAD.d.ts');
// Types in the global namespace for top-level scripts.
// This could be improved in the future. API-Extractor intentionally doesn't
// global variables, so another tool may be a better fit.
monaco.languages.typescript.typescriptDefaults.addExtraLib(
(await (await fetch('/manifoldCADGlobals.d.ts')).text())
(await (await fetch(manifoldCADGlobalsTypesUrl)).text())
.replace(/^export /gm, ''));
// Load up all scripts so that monaco can check types of multi-file models.
@@ -338,13 +347,28 @@ async function createEditor() {
const typeIndicator = document.getElementById('type-indicator');
self.window.typecache = new LocalStorageCache();
// We inject manifold-3d typings locally above, so avoid extra CDN probes for
// that package path. This prevents noisy 404s in production logs.
const jsDelivrResolver = new JsDelivrSourceResolver();
const sourceResolver = {
resolvePackageJson: async (packageName, version, subPath) => {
if (packageName === 'manifold-3d') return '';
return jsDelivrResolver.resolvePackageJson(packageName, version, subPath);
},
resolveSourceFile: async (packageName, version, path) => {
if (packageName === 'manifold-3d') return '';
return jsDelivrResolver.resolveSourceFile(packageName, version, path);
}
};
const autoTypings = await AutoTypings.create(editor, {
sourceResolver,
sourceCache: self.window.typecache,
onError: e => {
console.error(e);
if (typeIndicator) typeIndicator.textContent = '';
},
onUpdate: (update, text) => {
onUpdate: (_, text) => {
if (typeIndicator) typeIndicator.textContent = 'Fetching types...';
console.debug(text);
},
@@ -15,27 +15,49 @@
// Increment version when updating CDN URLs to clean up cache.
const cacheName = 'manifoldCAD-cache-v1';
self.addEventListener('install', e => {
e.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', e => {
e.waitUntil(caches.keys().then((keyList) => {
return Promise.all(keyList.map((key) => {
if (key !== cacheName) {
return caches.delete(key);
}
}));
}));
e.waitUntil(caches.keys()
.then((keyList) => {
return Promise.all(keyList.map((key) => {
if (key !== cacheName) {
return caches.delete(key);
}
}));
})
.then(() => self.clients.claim()));
});
// Serves from cache, then updates the cache in the background from the network,
// if available. Update available on refresh.
self.addEventListener(
'fetch',
e => {e.respondWith(caches.match(e.request).then(cachedResponse => {
const networkFetch = fetch(e.request).then(response => {
const clone = response.clone();
caches.open(cacheName).then(cache => {
cache.put(e.request, clone);
});
return response;
});
return cachedResponse || networkFetch;
}))});
self.addEventListener('fetch', e => {
if (e.request.method !== 'GET') {
return;
}
e.respondWith((async () => {
const cachedResponse = await caches.match(e.request);
try {
const response = await fetch(e.request);
// Only cache successful basic and opaque responses.
if (response.ok || response.type === 'opaque') {
const cache = await caches.open(cacheName);
await cache.put(e.request, response.clone());
}
return response;
} catch (error) {
if (cachedResponse) {
return cachedResponse;
}
console.debug('Service worker fetch failed:', e.request.url, error);
return Response.error();
}
})());
});