msw
Version:
97 lines (83 loc) • 3.5 kB
text/typescript
import { until } from '@open-draft/until'
import { devUtils } from '~/core/utils/internal/devUtils'
import { getAbsoluteWorkerUrl } from '../../../utils/getAbsoluteWorkerUrl'
import { getWorkerByRegistration } from './getWorkerByRegistration'
import { ServiceWorkerInstanceTuple, FindWorker } from '../../glossary'
/**
* Returns an active Service Worker instance.
* When not found, registers a new Service Worker.
*/
export const getWorkerInstance = async (
url: string,
options: RegistrationOptions = {},
findWorker: FindWorker,
): Promise<ServiceWorkerInstanceTuple> => {
// Resolve the absolute Service Worker URL.
const absoluteWorkerUrl = getAbsoluteWorkerUrl(url)
const mockRegistrations = await navigator.serviceWorker
.getRegistrations()
.then((registrations) =>
registrations.filter((registration) =>
getWorkerByRegistration(registration, absoluteWorkerUrl, findWorker),
),
)
if (!navigator.serviceWorker.controller && mockRegistrations.length > 0) {
// Reload the page when it has associated workers, but no active controller.
// The absence of a controller can mean either:
// - page has no Service Worker associated with it
// - page has been hard-reloaded and its workers won't be used until the next reload.
// Since we've checked that there are registrations associated with this page,
// at this point we are sure it's hard reload that falls into this clause.
location.reload()
}
const [existingRegistration] = mockRegistrations
if (existingRegistration) {
// Schedule the worker update in the background.
// Update ensures the existing worker is up-to-date.
existingRegistration.update()
// Return the worker reference immediately.
return [
getWorkerByRegistration(
existingRegistration,
absoluteWorkerUrl,
findWorker,
),
existingRegistration,
]
}
// When the Service Worker wasn't found, register it anew and return the reference.
const registrationResult = await until<Error, ServiceWorkerInstanceTuple>(
async () => {
const registration = await navigator.serviceWorker.register(url, options)
return [
// Compare existing worker registration by its worker URL,
// to prevent irrelevant workers to resolve here (such as Codesandbox worker).
getWorkerByRegistration(registration, absoluteWorkerUrl, findWorker),
registration,
]
},
)
// Handle Service Worker registration errors.
if (registrationResult.error) {
const isWorkerMissing = registrationResult.error.message.includes('(404)')
// Produce a custom error message when given a non-existing Service Worker url.
// Suggest developers to check their setup.
if (isWorkerMissing) {
const scopeUrl = new URL(options?.scope || '/', location.href)
throw new Error(
devUtils.formatMessage(`\
Failed to register a Service Worker for scope ('${scopeUrl.href}') with script ('${absoluteWorkerUrl}'): Service Worker script does not exist at the given path.
Did you forget to run "npx msw init <PUBLIC_DIR>"?
Learn more about creating the Service Worker script: https://mswjs.io/docs/cli/init`),
)
}
// Fallback error message for any other registration errors.
throw new Error(
devUtils.formatMessage(
'Failed to register the Service Worker:\n\n%s',
registrationResult.error.message,
),
)
}
return registrationResult.data
}