go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/sw/ui_sw.ts (about)

     1  // Copyright 2021 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // TODO(weiweilin): add integration tests to ensure the SW works properly.
    16  
    17  import 'virtual:configs.js';
    18  import {
    19    cleanupOutdatedCaches,
    20    createHandlerBoundToURL,
    21    precacheAndRoute,
    22  } from 'workbox-precaching';
    23  import { NavigationRoute, registerRoute } from 'workbox-routing';
    24  
    25  import { Prefetcher } from '@/common/service_workers/prefetch';
    26  
    27  // Tell TSC that this is a ServiceWorker script.
    28  declare const self: ServiceWorkerGlobalScope;
    29  
    30  /**
    31   * A regex for the defined routes.
    32   */
    33  declare const DEFINED_ROUTES_REGEXP: string;
    34  
    35  // Unconditionally skip waiting so the clients can always get the newest version
    36  // when the page is refreshed. The only downside is that clients on an old
    37  // version may encounter errors when lazy loading cached static assets. This is
    38  // not critical because
    39  // 1. This is unlikely to happen since all JS assets are prefetched.
    40  // 2. All lazy-loadable assets (i.e. excluding entry files) have content hashes
    41  //    in their filenames. This means in case of a cache miss,
    42  //    1. it's virtually impossible to lazy load an asset of an incompatible
    43  //       version because the content hash won't match, and
    44  //    2. they can have a much longer cache duration (currently configured to be
    45  //       4 weeks), the asset loading request can likely be fulfilled by other
    46  //       cache layers (e.g. AppEngine server cache, browser HTTP cache).
    47  // 3. When the client failed to lazy load an asset, a simple refresh will be
    48  //    able to fix the issue anyway.
    49  self.skipWaiting();
    50  
    51  const prefetcher = new Prefetcher(SETTINGS, self.fetch.bind(self));
    52  
    53  self.addEventListener('fetch', async (e) => {
    54    if (e.request.mode === 'navigate') {
    55      const url = new URL(e.request.url);
    56      prefetcher.prefetchResources(url.pathname);
    57      return;
    58    }
    59  
    60    if (prefetcher.respondWithPrefetched(e)) {
    61      return;
    62    }
    63  
    64    // Ensure all clients served by this service worker use the same config.
    65    if (e.request.url === self.origin + '/configs.js') {
    66      const res = new Response(
    67        `self.VERSION = '${VERSION}';\n` +
    68          `self.SETTINGS = Object.freeze(${JSON.stringify(SETTINGS)});\n`,
    69      );
    70      res.headers.set('content-type', 'application/javascript');
    71      e.respondWith(res);
    72      return;
    73    }
    74  });
    75  
    76  // Enable workbox specific features AFTER we registered our own event handlers
    77  // to make sure our own event handlers have the chance to intercept the
    78  // requests.
    79  {
    80    self.addEventListener('message', (event) => {
    81      // This is not used currently. We keep it here anyway to ensure that calling
    82      // `Workbox.prototype.messageSkipWaiting` is not a noop should it be used in
    83      // the future.
    84      if (event.data?.type === 'SKIP_WAITING') {
    85        self.skipWaiting();
    86      }
    87    });
    88  
    89    cleanupOutdatedCaches();
    90    precacheAndRoute(self.__WB_MANIFEST);
    91    registerRoute(
    92      new NavigationRoute(createHandlerBoundToURL('/ui/index.html'), {
    93        // Only handle defined routes so when the user visits a newly added route,
    94        // the service worker won't serve an old cache, causing the user to see a
    95        // 404 page until the new version is activated.
    96        allowlist: [new RegExp(DEFINED_ROUTES_REGEXP, 'i')],
    97      }),
    98    );
    99  }