-
-
Notifications
You must be signed in to change notification settings - Fork 469
/
createStartHandler.ts
142 lines (116 loc) · 5.57 KB
/
createStartHandler.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import { until } from '@open-draft/until'
import { getWorkerInstance } from './utils/getWorkerInstance'
import { enableMocking } from './utils/enableMocking'
import { SetupWorkerInternalContext, StartHandler } from '../glossary'
import { createRequestListener } from '../../utils/worker/createRequestListener'
import { requestIntegrityCheck } from '../../utils/internal/requestIntegrityCheck'
import { deferNetworkRequestsUntil } from '../../utils/deferNetworkRequestsUntil'
import { createResponseListener } from '../../utils/worker/createResponseListener'
import { validateWorkerScope } from './utils/validateWorkerScope'
import { devUtils } from '../../utils/internal/devUtils'
export const createStartHandler = (
context: SetupWorkerInternalContext,
): StartHandler => {
return function start(options, customOptions) {
const startWorkerInstance = async () => {
// Remove all previously existing event listeners.
// This way none of the listeners persists between Fast refresh
// of the application's code.
context.events.removeAllListeners()
// Handle requests signaled by the worker.
context.workerChannel.on(
'REQUEST',
createRequestListener(context, options),
)
context.workerChannel.on('RESPONSE', createResponseListener(context))
const instance = await getWorkerInstance(
options.serviceWorker.url,
options.serviceWorker.options,
options.findWorker,
)
const [worker, registration] = instance
if (!worker) {
const missingWorkerMessage = customOptions?.findWorker
? devUtils.formatMessage(
`Failed to locate the Service Worker registration using a custom "findWorker" predicate.
Please ensure that the custom predicate properly locates the Service Worker registration at "%s".
More details: https://mswjs.io/docs/api/setup-worker/start#findworker
`,
options.serviceWorker.url,
)
: devUtils.formatMessage(
`Failed to locate the Service Worker registration.
This most likely means that the worker script URL "%s" cannot resolve against the actual public hostname (%s). This may happen if your application runs behind a proxy, or has a dynamic hostname.
Please consider using a custom "serviceWorker.url" option to point to the actual worker script location, or a custom "findWorker" option to resolve the Service Worker registration manually. More details: https://mswjs.io/docs/api/setup-worker/start`,
options.serviceWorker.url,
location.host,
)
throw new Error(missingWorkerMessage)
}
context.worker = worker
context.registration = registration
context.events.addListener(window, 'beforeunload', () => {
if (worker.state !== 'redundant') {
// Notify the Service Worker that this client has closed.
// Internally, it's similar to disabling the mocking, only
// client close event has a handler that self-terminates
// the Service Worker when there are no open clients.
context.workerChannel.send('CLIENT_CLOSED')
}
// Make sure we're always clearing the interval - there are reports that not doing this can
// cause memory leaks in headless browser environments.
window.clearInterval(context.keepAliveInterval)
})
// Check if the active Service Worker is the latest published one
const [integrityError] = await until(() =>
requestIntegrityCheck(context, worker),
)
if (integrityError) {
devUtils.error(`\
Detected outdated Service Worker: ${integrityError.message}
The mocking is still enabled, but it's highly recommended that you update your Service Worker by running:
$ npx msw init <PUBLIC_DIR>
This is necessary to ensure that the Service Worker is in sync with the library to guarantee its stability.
If this message still persists after updating, please report an issue: https://github.com/open-draft/msw/issues\
`)
}
context.keepAliveInterval = window.setInterval(
() => context.workerChannel.send('KEEPALIVE_REQUEST'),
5000,
)
// Warn the user when loading the page that lies outside
// of the worker's scope.
validateWorkerScope(registration, context.startOptions)
return registration
}
const workerRegistration = startWorkerInstance().then(
async (registration) => {
const pendingInstance = registration.installing || registration.waiting
// Wait until the worker is activated.
// Assume the worker is already activated if there's no pending registration
// (i.e. when reloading the page after a successful activation).
if (pendingInstance) {
await new Promise<void>((resolve) => {
pendingInstance.addEventListener('statechange', () => {
if (pendingInstance.state === 'activated') {
return resolve()
}
})
})
}
// Print the activation message only after the worker has been activated.
await enableMocking(context, options).catch((error) => {
throw new Error(`Failed to enable mocking: ${error?.message}`)
})
return registration
},
)
// Defer any network requests until the Service Worker instance is ready.
// This prevents a race condition between the Service Worker registration
// and application's runtime requests (i.e. requests on mount).
if (options.waitUntilReady) {
deferNetworkRequestsUntil(workerRegistration)
}
return workerRegistration
}
}