diff --git a/.changeset/heavy-ravens-crash.md b/.changeset/heavy-ravens-crash.md new file mode 100644 index 00000000..cc8efbc2 --- /dev/null +++ b/.changeset/heavy-ravens-crash.md @@ -0,0 +1,5 @@ +--- +'thebe-lite': minor +--- + +Added a custom service worker for jupyterlite based on the default jupyterlite sw but with no caching, added build steps for this to be included in `thebe-lite` builds. diff --git a/packages/lite/src/jlite.ts b/packages/lite/src/jlite.ts index 708b5a81..df3db8c0 100644 --- a/packages/lite/src/jlite.ts +++ b/packages/lite/src/jlite.ts @@ -1,7 +1,7 @@ import type { ServiceManager } from '@jupyterlab/services'; +import type { LiteServerConfig } from './types'; import { PageConfig } from '@jupyterlab/coreutils'; import { JupyterLiteServer } from '@jupyterlite/server'; -import { LiteServerConfig } from './types'; const serverExtensions = [import('@jupyterlite/server-extension')]; diff --git a/packages/lite/src/service-worker.js b/packages/lite/src/service-worker.js new file mode 100644 index 00000000..d3736d3d --- /dev/null +++ b/packages/lite/src/service-worker.js @@ -0,0 +1,83 @@ +'use strict'; +// eslint-disable-next-line @typescript-eslint/triple-slash-reference +/// +/** + * reproduced and reduced from https://github.com/jupyterlite/jupyterlite/blob/main/packages/server/src/service-worker.ts + * to remove caching and other features not needed for thebe + */ + +/** + * Communication channel for drive access + */ +const broadcast = new BroadcastChannel('/api/drive.v1'); +/** + * Install event listeners + */ +self.addEventListener('install', onInstall); +self.addEventListener('activate', onActivate); +self.addEventListener('fetch', onFetch); +// Event handlers +/** + * Handle installation with the cache + */ +function onInstall(event) { + void self.skipWaiting(); +} +/** + * Handle activation. + */ +function onActivate(event) { + event.waitUntil(self.clients.claim()); +} +/** + * Handle fetching a single resource. + */ +async function onFetch(event) { + const { request } = event; + const url = new URL(event.request.url); + let responsePromise = null; + if (shouldBroadcast(url)) { + responsePromise = broadcastOne(request); + } else if (!shouldDrop(request, url)) { + // responsePromise = maybeFromCache(event); + const { request } = event; + responsePromise = await fetch(request); + } + if (responsePromise) { + event.respondWith(responsePromise); + } +} + +/** + * Whether a given URL should be broadcast + */ +function shouldBroadcast(url) { + return url.origin === location.origin && url.pathname.includes('/api/drive'); +} +/** + * Whether the fallback behavior should be used + */ +function shouldDrop(request, url) { + return ( + request.method !== 'GET' || url.origin.match(/^http/) === null || url.pathname.includes('/api/') + ); +} +/** + * Forward request to main using the broadcast channel + */ +async function broadcastOne(request) { + const promise = new Promise((resolve) => { + broadcast.onmessage = (event) => { + resolve(new Response(JSON.stringify(event.data))); + }; + }); + const message = await request.json(); + // Mark message as being for broadcast.ts + // This makes sure we won't get problems with messages + // across tabs with multiple notebook tabs open + message.receiver = 'broadcast.ts'; + broadcast.postMessage(message); + return await promise; +} + +//# sourceMappingURL=service-worker.js.map diff --git a/packages/lite/webpack.config.cjs b/packages/lite/webpack.config.cjs index 7360909c..15f0fc24 100644 --- a/packages/lite/webpack.config.cjs +++ b/packages/lite/webpack.config.cjs @@ -35,6 +35,10 @@ module.exports = { to: 'pypi', context: path.dirname(require.resolve('@jupyterlite/pyodide-kernel')), }, + { + from: './src/service-worker.js', + to: './', + }, ], }), ], @@ -58,6 +62,13 @@ module.exports = { use: 'ts-loader', exclude: /node_modules/, }, + { + resourceQuery: /text/, + type: 'asset/resource', + generator: { + filename: '[name][ext]', + }, + }, ], }, resolve: {