service worker in vscode

Chinese documentation: https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API

The role of service workers

The effect of referencing a service worker in JavaScriptis to enable offline support for the site. A service worker is a network proxy that sits between the web application and the browser network layer. Service workers intercept and process network requests from web applications and decide how to respond to those requests, including serving data from caches for offline support.
Code that references a service worker JavaScripttypically checks whether the browser supports service workers and registers the service worker so that Webthe application can be installed and activated when it is available. Once a service worker is registered, it can cache application resources for offline support, and perform other advanced operations such as push notifications and background synchronization.
Overall, code that references service workers JavaScriptcan Webprovide a richer and more dynamic user experience for applications and improve the performance and usability of websites.

To implement Service Worker in JavaScript, you generally need to follow the following steps:

1. To register Service Worker,
you must first register Service Worker in your web application. The following code can be written in the application javascript file:

if ('serviceWorker' in navigator) {
    
    
  navigator.serviceWorker.register('/service-worker.js')
    .then(function(registration) {
    
    
      console.log('Service Worker registered with scope:', registration.scope);
    }).catch(function(error) {
    
    
      console.error('Service Worker registration failed:', error);
    });
}

The main function of the above code is to check whether the browser supports Service Worker, and register Service Worker if the browser supports it.

  1. Defining the Service Worker lifecycle
    In a Service Worker, there are three key lifecycle events: install, activate, and fetch.
    The install event is used to pre-cache the application's resources after the Service Worker is installed. Cache resources, such as CSS, JS, pictures, etc., by listening to this event. Here is an example:
self.addEventListener('install', function(event) {
    
    
  event.waitUntil(
    caches.open('myCache')
      .then(function(cache) {
    
    
        return cache.addAll([
          '/index.html',
          '/bundle.js',
          '/styles.css'
        ]);
      })
  );
});

The main function of the above code is to pre-cache the specified files when the Service Worker is installed. After the Service Worker is installed, these files can be cached in the browser's local storage.
The activate event is used to clear old caches. You can compare the file names of the new and old cache files in the activate event. If the latest cache file name is inconsistent with the old cache file name, delete the old cache. Here is an example:

self.addEventListener('activate', function(event) {
    
    
  event.waitUntil(
    caches.keys().then(function(cacheNames) {
    
    
      return Promise.all(
        cacheNames.filter(function(cacheName) {
    
    
          return cacheName !== 'myCache';
        }).map(function(cacheName) {
    
    
          return caches.delete(cacheName);
        })
      );
    })
  );
});

After the Service Worker determines which resources should be cached, it can use the fetch event to intercept network requests and return cached resources. Here is an example:

self.addEventListener('fetch', function(event) {
    
    
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
    
    
        if (response) {
    
    
          return response;
        }
        return fetch(event.request);
      })
  );
});

In the preceding code, when a network request matches a cached file, the file is returned directly from the cache. If the request is not cached, it uses JavaScript's fetch function to fetch the resource from the network.
The above is the process and code example of the basic implementation of Service Worker. After Service Workerthe installation is complete, the browser will automatically register and monitor Service Workerthe life cycle events, and implement functions such as offline access and page loading speed optimization according to the code logic.

const workerReady = new Promise((resolve, reject) => {
    
    
	if (!areServiceWorkersEnabled()) {
    
    
		return reject(new Error('Service Workers are not enabled. Webviews will not work. Try disabling private/incognito mode.'));
	}

	const swPath = `service-worker.js?v=${
      
      expectedWorkerVersion}&vscode-resource-base-authority=${
      
      searchParams.get('vscode-resource-base-authority')}&remoteAuthority=${
      
      searchParams.get('remoteAuthority') ?? ''}`;
	navigator.serviceWorker.register(swPath)
		.then(() => navigator.serviceWorker.ready)
		.then(async registration => {
    
    
			/**
			 * @param {MessageEvent} event
			 */
			const versionHandler = async (event) => {
    
    
				if (event.data.channel !== 'version') {
    
    
					return;
				}

				navigator.serviceWorker.removeEventListener('message', versionHandler);
				if (event.data.version === expectedWorkerVersion) {
    
    
					return resolve();
				} else {
    
    
					console.log(`Found unexpected service worker version. Found: ${
      
      event.data.version}. Expected: ${
      
      expectedWorkerVersion}`);
					console.log(`Attempting to reload service worker`);

					// If we have the wrong version, try once (and only once) to unregister and re-register
					// Note that `.update` doesn't seem to work desktop electron at the moment so we use
					// `unregister` and `register` here.
					return registration.unregister()
						.then(() => navigator.serviceWorker.register(swPath))
						.then(() => navigator.serviceWorker.ready)
						.finally(() => {
    
     resolve(); });
				}
			};
			navigator.serviceWorker.addEventListener('message', versionHandler);

			const postVersionMessage = (/** @type {ServiceWorker} */ controller) => {
    
    
				controller.postMessage({
    
     channel: 'version' });
			};

			// At this point, either the service worker is ready and
			// became our controller, or we need to wait for it.
			// Note that navigator.serviceWorker.controller could be a
			// controller from a previously loaded service worker.
			const currentController = navigator.serviceWorker.controller;
			if (currentController?.scriptURL.endsWith(swPath)) {
    
    
				// service worker already loaded & ready to receive messages
				postVersionMessage(currentController);
			} else {
    
    
				// either there's no controlling service worker, or it's an old one:
				// wait for it to change before posting the message
				const onControllerChange = () => {
    
    
					navigator.serviceWorker.removeEventListener('controllerchange', onControllerChange);
					postVersionMessage(navigator.serviceWorker.controller);
				};
				navigator.serviceWorker.addEventListener('controllerchange', onControllerChange);
			}
		}).catch(error => {
    
    
			reject(new Error(`Could not register service workers: ${
      
      error}.`));
		});
});

service-worker.js

/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
// @ts-check

/// <reference no-default-lib="true"/>
/// <reference lib="webworker" />

const sw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {any} */ (self));

const VERSION = 4;

const resourceCacheName = `vscode-resource-cache-${
      
      VERSION}`;

const rootPath = sw.location.pathname.replace(/\/service-worker.js$/, '');

const searchParams = new URL(location.toString()).searchParams;

const remoteAuthority = searchParams.get('remoteAuthority');

/**
 * Origin used for resources
 */
const resourceBaseAuthority = searchParams.get('vscode-resource-base-authority');

const resolveTimeout = 30000;

/**
 * @template T
 * @typedef {
    
    {
 *     resolve: (x: T) => void,
 *     promise: Promise<T>
 * }} RequestStoreEntry
 */

/**
 * Caches
 * @template T
 */
class RequestStore {
    
    
	constructor() {
    
    
		/** @type {Map<number, RequestStoreEntry<T>>} */
		this.map = new Map();

		this.requestPool = 0;
	}

	/**
	 * @param {number} requestId
	 * @return {Promise<T> | undefined}
	 */
	get(requestId) {
    
    
		const entry = this.map.get(requestId);
		return entry && entry.promise;
	}

	/**
	 * @returns {
    
    { requestId: number, promise: Promise<T> }}
	 */
	create() {
    
    
		const requestId = ++this.requestPool;

		/** @type {undefined | ((x: T) => void)} */
		let resolve;

		/** @type {Promise<T>} */
		const promise = new Promise(r => resolve = r);

		/** @type {RequestStoreEntry<T>} */
		const entry = {
    
     resolve: /** @type {(x: T) => void} */ (resolve), promise };

		this.map.set(requestId, entry);

		const dispose = () => {
    
    
			clearTimeout(timeout);
			const existingEntry = this.map.get(requestId);
			if (existingEntry === entry) {
    
    
				return this.map.delete(requestId);
			}
		};
		const timeout = setTimeout(dispose, resolveTimeout);
		return {
    
     requestId, promise };
	}

	/**
	 * @param {number} requestId
	 * @param {T} result
	 * @return {boolean}
	 */
	resolve(requestId, result) {
    
    
		const entry = this.map.get(requestId);
		if (!entry) {
    
    
			return false;
		}
		entry.resolve(result);
		this.map.delete(requestId);
		return true;
	}
}

/**
 * @typedef {
    
    { readonly status: 200; id: number; path: string; mime: string; data: Uint8Array; etag: string | undefined; mtime: number | undefined; }
 * 		| { readonly status: 304; id: number; path: string; mime: string; mtime: number | undefined }
 *		| { readonly status: 401; id: number; path: string }
 *		| { readonly status: 404; id: number; path: string }} ResourceResponse
 */

/**
 * Map of requested paths to responses.
 *
 * @type {RequestStore<ResourceResponse>}
 */
const resourceRequestStore = new RequestStore();

/**
 * Map of requested localhost origins to optional redirects.
 *
 * @type {RequestStore<string | undefined>}
 */
const localhostRequestStore = new RequestStore();

const unauthorized = () =>
	new Response('Unauthorized', {
    
     status: 401, });

const notFound = () =>
	new Response('Not Found', {
    
     status: 404, });

const methodNotAllowed = () =>
	new Response('Method Not Allowed', {
    
     status: 405, });

sw.addEventListener('message', async (event) => {
    
    
	switch (event.data.channel) {
    
    
		case 'version':
			{
    
    
				const source = /** @type {Client} */ (event.source);
				sw.clients.get(source.id).then(client => {
    
    
					if (client) {
    
    
						client.postMessage({
    
    
							channel: 'version',
							version: VERSION
						});
					}
				});
				return;
			}
		case 'did-load-resource':
			{
    
    
				/** @type {ResourceResponse} */
				const response = event.data.data;
				if (!resourceRequestStore.resolve(response.id, response)) {
    
    
					console.log('Could not resolve unknown resource', response.path);
				}
				return;
			}
		case 'did-load-localhost':
			{
    
    
				const data = event.data.data;
				if (!localhostRequestStore.resolve(data.id, data.location)) {
    
    
					console.log('Could not resolve unknown localhost', data.origin);
				}
				return;
			}
		default:
			console.log('Unknown message');
			return;
	}
});

sw.addEventListener('fetch', (event) => {
    
    
	const requestUrl = new URL(event.request.url);
	if (requestUrl.protocol === 'https:' && requestUrl.hostname.endsWith('.' + resourceBaseAuthority)) {
    
    
		switch (event.request.method) {
    
    
			case 'GET':
			case 'HEAD': {
    
    
				const firstHostSegment = requestUrl.hostname.slice(0, requestUrl.hostname.length - (resourceBaseAuthority.length + 1));
				const scheme = firstHostSegment.split('+', 1)[0];
				const authority = firstHostSegment.slice(scheme.length + 1); // may be empty
				return event.respondWith(processResourceRequest(event, {
    
    
					scheme,
					authority,
					path: requestUrl.pathname,
					query: requestUrl.search.replace(/^\?/, ''),
				}));
			}
			default:
				return event.respondWith(methodNotAllowed());
		}
	}

	// If we're making a request against the remote authority, we want to go
	// back through VS Code itself so that we are authenticated properly
	if (requestUrl.host === remoteAuthority) {
    
    
		switch (event.request.method) {
    
    
			case 'GET':
			case 'HEAD':
				return event.respondWith(processResourceRequest(event, {
    
    
					path: requestUrl.pathname,
					scheme: requestUrl.protocol.slice(0, requestUrl.protocol.length - 1),
					authority: requestUrl.host,
					query: requestUrl.search.replace(/^\?/, ''),
				}));

			default:
				return event.respondWith(methodNotAllowed());
		}
	}

	// See if it's a localhost request
	if (requestUrl.origin !== sw.origin && requestUrl.host.match(/^(localhost|127.0.0.1|0.0.0.0):(\d+)$/)) {
    
    
		return event.respondWith(processLocalhostRequest(event, requestUrl));
	}
});

sw.addEventListener('install', (event) => {
    
    
	event.waitUntil(sw.skipWaiting()); // Activate worker immediately
});

sw.addEventListener('activate', (event) => {
    
    
	event.waitUntil(sw.clients.claim()); // Become available to all pages
});

/**
 * @param {FetchEvent} event
 * @param {
    
    {
 * 		scheme: string;
 * 		authority: string;
 * 		path: string;
 * 		query: string;
 * }} requestUrlComponents
 */
async function processResourceRequest(event, requestUrlComponents) {
    
    
	const client = await sw.clients.get(event.clientId);
	if (!client) {
    
    
		console.error('Could not find inner client for request');
		return notFound();
	}

	const webviewId = getWebviewIdForClient(client);
	if (!webviewId) {
    
    
		console.error('Could not resolve webview id');
		return notFound();
	}

	const shouldTryCaching = (event.request.method === 'GET');

	/**
	 * @param {ResourceResponse} entry
	 * @param {Response | undefined} cachedResponse
	 */
	const resolveResourceEntry = (entry, cachedResponse) => {
    
    
		if (entry.status === 304) {
    
     // Not modified
			if (cachedResponse) {
    
    
				return cachedResponse.clone();
			} else {
    
    
				throw new Error('No cache found');
			}
		}

		if (entry.status === 401) {
    
    
			return unauthorized();
		}

		if (entry.status !== 200) {
    
    
			return notFound();
		}

		/** @type {Record<string, string>} */
		const headers = {
    
    
			'Content-Type': entry.mime,
			'Content-Length': entry.data.byteLength.toString(),
			'Access-Control-Allow-Origin': '*',
		};
		if (entry.etag) {
    
    
			headers['ETag'] = entry.etag;
			headers['Cache-Control'] = 'no-cache';
		}
		if (entry.mtime) {
    
    
			headers['Last-Modified'] = new Date(entry.mtime).toUTCString();
		}
		const response = new Response(entry.data, {
    
    
			status: 200,
			headers
		});

		if (shouldTryCaching && entry.etag) {
    
    
			caches.open(resourceCacheName).then(cache => {
    
    
				return cache.put(event.request, response);
			});
		}
		return response.clone();
	};

	const parentClients = await getOuterIframeClient(webviewId);
	if (!parentClients.length) {
    
    
		console.log('Could not find parent client for request');
		return notFound();
	}

	/** @type {Response | undefined} */
	let cached;
	if (shouldTryCaching) {
    
    
		const cache = await caches.open(resourceCacheName);
		cached = await cache.match(event.request);
	}

	const {
    
     requestId, promise } = resourceRequestStore.create();

	for (const parentClient of parentClients) {
    
    
		parentClient.postMessage({
    
    
			channel: 'load-resource',
			id: requestId,
			scheme: requestUrlComponents.scheme,
			authority: requestUrlComponents.authority,
			path: requestUrlComponents.path,
			query: requestUrlComponents.query,
			ifNoneMatch: cached?.headers.get('ETag'),
		});
	}

	return promise.then(entry => resolveResourceEntry(entry, cached));
}

/**
 * @param {FetchEvent} event
 * @param {URL} requestUrl
 * @return {Promise<Response>}
 */
async function processLocalhostRequest(event, requestUrl) {
    
    
	const client = await sw.clients.get(event.clientId);
	if (!client) {
    
    
		// This is expected when requesting resources on other localhost ports
		// that are not spawned by vs code
		return fetch(event.request);
	}
	const webviewId = getWebviewIdForClient(client);
	if (!webviewId) {
    
    
		console.error('Could not resolve webview id');
		return fetch(event.request);
	}

	const origin = requestUrl.origin;

	/**
	 * @param {string | undefined} redirectOrigin
	 * @return {Promise<Response>}
	 */
	const resolveRedirect = async (redirectOrigin) => {
    
    
		if (!redirectOrigin) {
    
    
			return fetch(event.request);
		}
		const location = event.request.url.replace(new RegExp(`^${
      
      requestUrl.origin}(/|$)`), `${
      
      redirectOrigin}$1`);
		return new Response(null, {
    
    
			status: 302,
			headers: {
    
    
				Location: location
			}
		});
	};

	const parentClients = await getOuterIframeClient(webviewId);
	if (!parentClients.length) {
    
    
		console.log('Could not find parent client for request');
		return notFound();
	}

	const {
    
     requestId, promise } = localhostRequestStore.create();
	for (const parentClient of parentClients) {
    
    
		parentClient.postMessage({
    
    
			channel: 'load-localhost',
			origin: origin,
			id: requestId,
		});
	}

	return promise.then(resolveRedirect);
}

/**
 * @param {Client} client
 * @returns {string | null}
 */
function getWebviewIdForClient(client) {
    
    
	const requesterClientUrl = new URL(client.url);
	return requesterClientUrl.searchParams.get('id');
}

/**
 * @param {string} webviewId
 * @returns {Promise<Client[]>}
 */
async function getOuterIframeClient(webviewId) {
    
    
	const allClients = await sw.clients.matchAll({
    
     includeUncontrolled: true });
	return allClients.filter(client => {
    
    
		const clientUrl = new URL(client.url);
		const hasExpectedPathName = (clientUrl.pathname === `${
      
      rootPath}/` || clientUrl.pathname === `${
      
      rootPath}/index.html` || clientUrl.pathname === `${
      
      rootPath}/index-no-csp.html`);
		return hasExpectedPathName && clientUrl.searchParams.get('id') === webviewId;
	});
}

https://blog.csdn.net/qq_41581588/article/details/126739689
https://www.cnblogs.com/dojo-lzz/p/8047336.html There is a video below

The difference between navigator.serviceWorker.register and new Worker

navigator.serviceWorker.register and new Worker are two completely different APIs.

  • navigator.serviceWorker.register:This is the registration API for Service Workers. Through this API, we can register a JavaScript code file as a Service Worker in the browser, and control functions such as web page caching, offline support, push notifications, and background data synchronization.
  • new Worker:This is the creation API for Web Workers. Through this API, we can register a JavaScript code file as a Worker in the browser and run it in a background thread. Web Workers can run in parallel with the main thread, so they are ideal for handling a lot of computationally intensive operations.
    In short, navigator.serviceWorker.register is used to register Service Worker, which is used to control web page caching, offline support and other functions ; while new Worker is to create Web Worker, which is used to run a JavaScript code file in parallel in the background thread .

How to understand the use of new workers, you can read my other article
https://blog.csdn.net/woyebuzhidao321/article/details/126618420

Guess you like

Origin blog.csdn.net/woyebuzhidao321/article/details/130089154