@lwc/hmr-client 10.2.1 → 10.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,38 @@
1
+ import JSONWebSocket from '../utils/json-web-socket';
2
+ import { TypedEventTarget } from '../utils/typed-event-target';
3
+ import { HotModuleHandler } from './hot-module-handler';
4
+ type HmrClientEvent = {
5
+ modulePath: string;
6
+ };
7
+ type HmrClientEvents = {
8
+ 'hmr:update': HmrClientEvent;
9
+ 'hmr:delete': HmrClientEvent;
10
+ 'hmr:create': HmrClientEvent;
11
+ 'hmr:hot-swapped': HmrClientEvent;
12
+ 'hmr:close': void;
13
+ };
14
+ export declare class HMRClient {
15
+ moduleHandlerHooks: HotModuleHandler;
16
+ ws: JSONWebSocket;
17
+ private eventTarget;
18
+ constructor(ws: JSONWebSocket, moduleHandlerHooks: HotModuleHandler);
19
+ /**
20
+ * Register all modules that have been registered at this point.
21
+ * This function is usually called upon hmr-client initialization.
22
+ */
23
+ registerAllModules(): void;
24
+ /**
25
+ * Register any new modules that have been registered.
26
+ */
27
+ registerPendingModules(): void;
28
+ messageCallback(data: unknown): void;
29
+ /**
30
+ * Send a message to the dev server to send a hot module.
31
+ * The send and receive messages are asyncronous.
32
+ * @param modulePath the module to be fetched
33
+ */
34
+ fetchModule(modulePath: string): void;
35
+ addEventListener: TypedEventTarget<HmrClientEvents>['addEventListener'];
36
+ removeEventListener: TypedEventTarget<HmrClientEvents>['removeEventListener'];
37
+ }
38
+ export {};
@@ -0,0 +1,16 @@
1
+ /**
2
+ * This interface defines capabilities that need to be implemented by the container to handle hot module
3
+ * updates.
4
+ */
5
+ export interface HotModuleHandler {
6
+ /**
7
+ * Fetch all dependencies that are required for a hot module before its evaluation.
8
+ * @param moduleSpecifiers List of dependent modules specifiers.
9
+ */
10
+ fetchDependencies(moduleSpecifiers: string[]): Promise<any>;
11
+ /**
12
+ * Evalaute a given Javascript source code.
13
+ * @param source Javascript in string format.
14
+ */
15
+ evaluateModule(ownerModuleId: string, hotModulePath: string, source: string): Promise<unknown>;
16
+ }
@@ -0,0 +1,18 @@
1
+ import { Auth_Challenge, Auth_Response } from '@lwc/lwc-dev-server-types';
2
+ import { HotModuleHandler } from './hot-module-handler';
3
+ import { HMRClient } from './client';
4
+ type AuthConfig = {
5
+ authChallengeData: Auth_Challenge;
6
+ authResponseCallback: (response: Auth_Response) => boolean;
7
+ };
8
+ /**
9
+ * Initialize the hmr-client.
10
+ * @param url The lightning dev preview server to connect to. Provide the fully qualified WebSocketServer url, for example 'wss://localhost:8999'
11
+ * @param target The type of client being served. Currently only supports 'LEX.
12
+ * @param hotModuleHandler Callback hooks that the hmr-client will utilize to handle hot modules. The callbacks are implemented by the container.
13
+ * @param authConfig Optional authentication configuration if the hmr-client needs to establish an authenticated connection.
14
+ *
15
+ * @returns {HMRClient} the HMR Client
16
+ */
17
+ export declare function initializeClient(url: string, _target: string, hotModuleHandler: HotModuleHandler, authConfig?: AuthConfig): Promise<HMRClient>;
18
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.cjs.js CHANGED
@@ -5,6 +5,54 @@
5
5
 
6
6
  Object.defineProperty(exports, '__esModule', { value: true });
7
7
 
8
+ var lwcDevServerTypes = require('@lwc/lwc-dev-server-types');
9
+
10
+ /**
11
+ * Wrapper on WebSocket to handle JSON serialization/deserialization
12
+ */
13
+ class JSONWebSocket {
14
+ constructor(url, protocols) {
15
+ this.socket = new WebSocket(url, protocols);
16
+ }
17
+ send(data) {
18
+ this.socket.send(JSON.stringify(data));
19
+ }
20
+ onMessage(callback) {
21
+ this.socket.addEventListener('message', (event) => {
22
+ const jsonData = JSON.parse(event.data);
23
+ callback(jsonData);
24
+ });
25
+ }
26
+ close() {
27
+ this.socket.close();
28
+ }
29
+ static init(url, protocols) {
30
+ return new Promise((resolve, reject) => {
31
+ try {
32
+ const socket = new JSONWebSocket(url, protocols);
33
+ socket.socket.onopen = () => resolve(socket);
34
+ socket.socket.onerror = (e) => reject(e);
35
+ }
36
+ catch (e) {
37
+ reject(e);
38
+ }
39
+ });
40
+ }
41
+ }
42
+
43
+ /* eslint-disable no-console */
44
+ /**
45
+ * All the console methods in one module so that these can be instrumented for JEST tests and
46
+ * marked for eslint ignore in one place.
47
+ */
48
+ const LABEL = '[lightning preview]';
49
+ function logWarning(message) {
50
+ console.warn(`${LABEL} ${message}`);
51
+ }
52
+
53
+ const IGNORING_PREAUTH_RESPONSES = 'Ignoring pre-authenticated responses sent from Lightning Dev Preview server';
54
+ const AUTH_FAILURE = 'WebSocket Authentication Failure: Failed to success fully establish an authenticated connection between Lightning Dev Preview server and client';
55
+
8
56
  const hotModuleCbs = new Map();
9
57
  /**
10
58
  * API call to self update.
@@ -95,239 +143,6 @@ function getActiveModules() {
95
143
  return Array.from(activeModules.values());
96
144
  }
97
145
 
98
- /**
99
- * This module is responsible for notfying the user about module updates in the server
100
- **/
101
- class NotifyModuleUpdate {
102
- constructor() {
103
- this.observers = new Set();
104
- }
105
- register(observer) {
106
- if (observer) {
107
- this.observers.add(observer);
108
- }
109
- }
110
- deregister(observer) {
111
- if (observer) {
112
- this.observers.delete(observer);
113
- }
114
- }
115
- notify(message) {
116
- this.observers.forEach((observer) => observer.notify(message));
117
- }
118
- }
119
-
120
- /* eslint-disable no-console */
121
- /**
122
- * All the console methods in one module so that these can be instrumented for JEST tests and
123
- * marked for eslint ignore in one place.
124
- */
125
- const LABEL = '[lightning preview]';
126
- function logWarning(message) {
127
- console.warn(`${LABEL} ${message}`);
128
- }
129
- function logInfo(message) {
130
- console.log(`${LABEL} ${message}`);
131
- }
132
-
133
- const SOCKET_OPEN = 'WebSocket: Connection to Lightning Dev Preview server open.';
134
- const SOCKET_CONNECTION_INITIATION = 'WebSocket: Connecting to Lightning Dev Preview server:';
135
- const SOCKET_SERVER_UNAVAILABLE = 'WebSocket Server Unavailable: Connection to Lightning Dev Preview server unavailable.';
136
- const SOCKET_SERVER_CONNECTION_ERROR = 'WebSocket Server Connection Error: Connection to Lightning Dev Preview server failed.';
137
- const IGNORING_PREAUTH_RESPONSES = 'Ignoring pre-authenticated responses sent from Lightning Dev Preview server';
138
- const AUTH_FAILURE = 'WebSocket Authentication Failure: Failed to success fully establish an authenticated connection between Lightning Dev Preview server and client';
139
-
140
- const CONNECTION_RETRY_COUNT = 5;
141
- const CONNECTION_RETRY_INTERVAL_MS = 500;
142
- async function getConnection(devServerUrl, target, clientId, authConfig, connectionOpenCallback, connectionCloseCallback) {
143
- const socket = await initiateWSConnection(devServerUrl, CONNECTION_RETRY_COUNT);
144
- connectionOpenCallback && connectionOpenCallback();
145
- if (connectionCloseCallback) {
146
- socket.addEventListener('close', connectionCloseCallback);
147
- }
148
- const connection = new Connection(socket);
149
- const initData = {
150
- type: 'init',
151
- data: {
152
- clientId,
153
- url: window.location.href,
154
- target,
155
- },
156
- };
157
- // If authentication is needed, resolve promise after successful authentication
158
- if (authConfig) {
159
- return new Promise((resolve, reject) => {
160
- // Create a listener to handle the auth response and remove it once the connection is authenticated
161
- const listener = createAuthMessageHandler((response) => {
162
- // Delegate auth response validation to invoker
163
- const authResult = authConfig.authResponseCallback(response);
164
- // If successful, resolve with a connection that is ready to be used
165
- if (authResult) {
166
- socket.removeEventListener('message', listener);
167
- resolve(connection);
168
- }
169
- else {
170
- reject(new Error(AUTH_FAILURE));
171
- }
172
- });
173
- socket.addEventListener('message', listener);
174
- initData.data.auth = {
175
- type: 'auth-challenge',
176
- data: authConfig.authChallengeData,
177
- };
178
- connection.send(initData);
179
- });
180
- }
181
- else {
182
- connection.send(initData);
183
- return connection;
184
- }
185
- }
186
- class Connection {
187
- constructor(socket) {
188
- this.socket = socket;
189
- }
190
- send(data) {
191
- if (!this.isReady()) {
192
- logWarning('Ignoring to send data before connection is ready.');
193
- return false;
194
- }
195
- this.socket.send(JSON.stringify(data));
196
- return true;
197
- }
198
- receive(callback) {
199
- if (!this.isReady()) {
200
- logWarning('Connection not ready to receive data from server.');
201
- return;
202
- }
203
- this.socket.addEventListener('message', ({ data }) => {
204
- if (data) {
205
- const payload = JSON.parse(data);
206
- callback(payload);
207
- }
208
- });
209
- }
210
- close() {
211
- this.socket.close();
212
- }
213
- isReady() {
214
- return this.socket.readyState === this.socket.OPEN;
215
- }
216
- }
217
- /**
218
- * A event handler to handle messages from the WebSocket.
219
- * This handler is meant to only be used for authentication and then detached after successful auth.
220
- */
221
- function createAuthMessageHandler(authCallback) {
222
- return ({ data }) => {
223
- if (data) {
224
- const payload = JSON.parse(data);
225
- // only accept auth-response messages from LDP Server until authenticated
226
- if (payload.type !== 'auth-response') {
227
- logWarning(IGNORING_PREAUTH_RESPONSES);
228
- return;
229
- }
230
- if (payload.type === 'auth-response') {
231
- const authResponse = payload;
232
- authCallback(authResponse.data);
233
- }
234
- }
235
- };
236
- }
237
- /**
238
- * A function to establish a WebSocket connection to the provided url
239
- * @param wsServerUrl The lightning dev server url to connect to
240
- * @param connectionRetry number of retry attempts to connect with the server
241
- * @returns
242
- */
243
- async function initiateWSConnection(wsServerUrl, connectionRetry) {
244
- let socket;
245
- let retryCount = 0;
246
- while (socket === undefined && retryCount < connectionRetry) {
247
- try {
248
- logInfo(`${SOCKET_CONNECTION_INITIATION} ${wsServerUrl}` + retryCount
249
- ? `, retry attempt: ${retryCount}`
250
- : '');
251
- socket = await connect(wsServerUrl);
252
- }
253
- catch (e) {
254
- // Nothing to do, the LDP server is not available yet.
255
- // Cannot tell if the server url is unavailable or invalid, regardless it will retry
256
- }
257
- if (socket !== undefined) {
258
- break;
259
- }
260
- retryCount++;
261
- await new Promise((resolve) => setTimeout(resolve, CONNECTION_RETRY_INTERVAL_MS));
262
- }
263
- if (socket === undefined) {
264
- logWarning(SOCKET_SERVER_UNAVAILABLE);
265
- throw new Error(SOCKET_SERVER_UNAVAILABLE);
266
- }
267
- return socket;
268
- }
269
- /**
270
- * Attempt to open a WebSockect connection
271
- * @param wsServerUrl WebSocketServer url to connect to
272
- * @returns Resolves with a successful socket connection, that is open to communication else rejects with an error message
273
- */
274
- function connect(wsServerUrl) {
275
- return new Promise((resolve, reject) => {
276
- try {
277
- const socket = new WebSocket(wsServerUrl, 'lightning-dev-preview');
278
- const errorHandler = (_e) => {
279
- logWarning(`${SOCKET_SERVER_CONNECTION_ERROR}.`);
280
- reject(SOCKET_SERVER_CONNECTION_ERROR);
281
- };
282
- socket.addEventListener('error', errorHandler);
283
- // The connection is ready when the open callback is called, resolve the promise then
284
- socket.addEventListener('open', () => {
285
- logInfo(SOCKET_OPEN);
286
- // Remove the error handler once the connection is open
287
- socket.removeEventListener('error', errorHandler);
288
- // The connection is ready when the open callback is called, resolve the promise then
289
- resolve(socket);
290
- });
291
- }
292
- catch (e) {
293
- logWarning(`${SOCKET_SERVER_CONNECTION_ERROR}. Cause: ${JSON.stringify(e)}`);
294
- reject(SOCKET_SERVER_CONNECTION_ERROR);
295
- }
296
- });
297
- }
298
-
299
- class ConsoleNotifier {
300
- notify(message) {
301
- switch (message.eventType) {
302
- case 'create':
303
- console.log(`A new module was added at ${message.modulePath}`);
304
- break;
305
- case 'delete':
306
- console.log(`A module was deleted at ${message.modulePath}`);
307
- break;
308
- case 'update':
309
- console.log(`A source change detected at ${message.modulePath}`);
310
- break;
311
- case 'hot-swapped':
312
- console.log(`Received a hot module with path ${message.modulePath}, attempting to hot swap`);
313
- break;
314
- }
315
- if (message.action) {
316
- console.log(`Taking the following action on module update ${message.action.message}`);
317
- }
318
- }
319
- }
320
-
321
- function debounce(callback, delay) {
322
- let timer;
323
- return () => {
324
- clearTimeout(timer);
325
- timer = window.setTimeout(() => {
326
- callback();
327
- }, delay);
328
- };
329
- }
330
-
331
146
  function extractDescriptorsFromSource(src) {
332
147
  const amdPattern = /^define\('([^']+)',\s*\[([^\]]*)\],/;
333
148
  const match = amdPattern.exec(src);
@@ -349,32 +164,86 @@ function adaptSourceForLex(src, hotModulePath, parentDescriptor) {
349
164
  .concat('}');
350
165
  }
351
166
 
352
- const MODULE_REGISTRATION_DELAY_MS = 100;
353
- // Create a random identifier for the client connection
354
- function generateRandomClientId() {
355
- const uid = typeof crypto !== 'undefined' ? crypto.randomUUID() : Math.round(Math.random() * 1000);
356
- return `${uid}`;
167
+ function debounce(callback, delay) {
168
+ let timer;
169
+ return () => {
170
+ clearTimeout(timer);
171
+ timer = setTimeout(() => {
172
+ callback();
173
+ }, delay);
174
+ };
175
+ }
176
+
177
+ /******************************************************************************
178
+ Copyright (c) Microsoft Corporation.
179
+
180
+ Permission to use, copy, modify, and/or distribute this software for any
181
+ purpose with or without fee is hereby granted.
182
+
183
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
184
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
185
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
186
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
187
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
188
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
189
+ PERFORMANCE OF THIS SOFTWARE.
190
+ ***************************************************************************** */
191
+ /* global Reflect, Promise, SuppressedError, Symbol */
192
+
193
+
194
+ function __classPrivateFieldGet(receiver, state, kind, f) {
195
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
196
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
197
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
198
+ }
199
+
200
+ function __classPrivateFieldSet(receiver, state, value, kind, f) {
201
+ if (kind === "m") throw new TypeError("Private method is not writable");
202
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
203
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
204
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
205
+ }
206
+
207
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
208
+ var e = new Error(message);
209
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
210
+ };
211
+
212
+ var _TypedEventTarget_target;
213
+ //TODO: move to notify folder
214
+ class TypedEventTarget {
215
+ constructor() {
216
+ _TypedEventTarget_target.set(this, void 0);
217
+ __classPrivateFieldSet(this, _TypedEventTarget_target, new EventTarget(), "f");
218
+ }
219
+ addEventListener(type, listener) {
220
+ __classPrivateFieldGet(this, _TypedEventTarget_target, "f").addEventListener(type, listener);
221
+ }
222
+ removeEventListener(type, listener) {
223
+ __classPrivateFieldGet(this, _TypedEventTarget_target, "f").removeEventListener(type, listener);
224
+ }
225
+ dispatch(type, detail) {
226
+ return __classPrivateFieldGet(this, _TypedEventTarget_target, "f").dispatchEvent(new CustomEvent(type, { detail }));
227
+ }
357
228
  }
229
+ _TypedEventTarget_target = new WeakMap();
230
+
231
+ const MODULE_REGISTRATION_DELAY_MS = 100;
358
232
  let hotModuleCounter = 0;
359
233
  class HMRClient {
360
- constructor(clientId, connection, moduleHandlerHooks) {
361
- this.notifyModuleUpdate = new NotifyModuleUpdate();
234
+ constructor(ws, moduleHandlerHooks) {
235
+ this.eventTarget = new TypedEventTarget();
236
+ this.addEventListener = (...args) => {
237
+ this.eventTarget.addEventListener(...args);
238
+ };
239
+ this.removeEventListener = (...args) => {
240
+ this.eventTarget.removeEventListener(...args);
241
+ };
242
+ if (ws.socket.readyState !== 1) {
243
+ throw new Error('Socket needs to be open');
244
+ }
362
245
  this.moduleHandlerHooks = moduleHandlerHooks;
363
- this.connection = connection;
364
- this.clientId = clientId;
365
- // Register a console logger
366
- this.notifyModuleUpdate.register(new ConsoleNotifier());
367
- this.init();
368
- }
369
- registerObserver(observer) {
370
- // Register any container specific observers
371
- this.notifyModuleUpdate.register(observer);
372
- }
373
- /**
374
- * Initialize the hmr-client.
375
- */
376
- init() {
377
- // Register all modules upon first connection
246
+ this.ws = ws;
378
247
  this.registerAllModules();
379
248
  // Add a debounced callback to register any new modules with server
380
249
  const registerPendingModulesDebounced = debounce(this.registerPendingModules.bind(this), MODULE_REGISTRATION_DELAY_MS);
@@ -382,7 +251,8 @@ class HMRClient {
382
251
  modulesPendingRegistration.add(modulePath);
383
252
  registerPendingModulesDebounced();
384
253
  });
385
- this.connection.receive(this.messageCallback.bind(this));
254
+ this.ws.onMessage(this.messageCallback.bind(this));
255
+ this.ws.socket.addEventListener('close', () => this.eventTarget.dispatch('hmr:close'));
386
256
  }
387
257
  /**
388
258
  * Register all modules that have been registered at this point.
@@ -393,10 +263,9 @@ class HMRClient {
393
263
  type: 'register',
394
264
  data: {
395
265
  modules: getActiveModules(),
396
- clientId: this.clientId,
397
266
  },
398
267
  };
399
- this.connection.send(moduleRegistration);
268
+ this.ws.send(moduleRegistration);
400
269
  modulesPendingRegistration.clear();
401
270
  }
402
271
  /**
@@ -408,15 +277,16 @@ class HMRClient {
408
277
  type: 'register',
409
278
  data: {
410
279
  modules: Array.from(modulesPendingRegistration).map((module) => activeModules.get(module)),
411
- clientId: this.clientId,
412
280
  },
413
281
  };
414
- this.connection.send(moduleRegistration);
282
+ this.ws.send(moduleRegistration);
415
283
  // We assume the registration was successful
416
284
  modulesPendingRegistration.clear();
417
285
  }
418
286
  }
419
287
  messageCallback(data) {
288
+ if (!lwcDevServerTypes.isHmrData(data))
289
+ return;
420
290
  switch (data.type) {
421
291
  // Some paths were updated
422
292
  case 'update': {
@@ -424,10 +294,7 @@ class HMRClient {
424
294
  moduleUpdate.data.forEach(({ modulePath }) => {
425
295
  // If there are update handlers for the modulePath, then fetch an update
426
296
  if (hasUpdateHandlers(modulePath)) {
427
- this.notifyModuleUpdate.notify({
428
- eventType: 'update',
429
- modulePath,
430
- });
297
+ this.eventTarget.dispatch('hmr:update', { modulePath });
431
298
  this.fetchModule(modulePath);
432
299
  }
433
300
  });
@@ -438,10 +305,7 @@ class HMRClient {
438
305
  const hotModules = data;
439
306
  // Process each incoming hot module
440
307
  hotModules.data.forEach(async ({ modulePath, src }) => {
441
- this.notifyModuleUpdate.notify({
442
- eventType: 'hot-swapped',
443
- modulePath,
444
- });
308
+ this.eventTarget.dispatch('hmr:hot-swapped', { modulePath });
445
309
  // Wrapped update handler, it will call the previous accept callbacks and remove
446
310
  // them after they have been invoked.
447
311
  const handler = updateHandler(modulePath);
@@ -466,10 +330,7 @@ class HMRClient {
466
330
  case 'module-delete': {
467
331
  const deletedModule = data;
468
332
  deletedModule.data.forEach(({ modulePath }) => {
469
- this.notifyModuleUpdate.notify({
470
- eventType: 'delete',
471
- modulePath,
472
- });
333
+ this.eventTarget.dispatch('hmr:delete', { modulePath });
473
334
  });
474
335
  // A module was deleted
475
336
  // reload page?
@@ -478,7 +339,7 @@ class HMRClient {
478
339
  case 'error':
479
340
  // eslint-disable-next-line no-console
480
341
  console.log('LWC dev server encountered an error, reloading page');
481
- window.location.reload();
342
+ location.reload();
482
343
  break;
483
344
  }
484
345
  }
@@ -491,26 +352,65 @@ class HMRClient {
491
352
  const fetchData = { type: 'fetch', data: { modulePath } };
492
353
  // fetch the new module from the server
493
354
  // eval it and return the new module
494
- this.connection.send(fetchData);
355
+ this.ws.send(fetchData);
495
356
  }
496
357
  }
358
+
497
359
  /**
498
360
  * Initialize the hmr-client.
499
361
  * @param url The lightning dev preview server to connect to. Provide the fully qualified WebSocketServer url, for example 'wss://localhost:8999'
500
362
  * @param target The type of client being served. Currently only supports 'LEX.
501
363
  * @param hotModuleHandler Callback hooks that the hmr-client will utilize to handle hot modules. The callbacks are implemented by the container.
502
364
  * @param authConfig Optional authentication configuration if the hmr-client needs to establish an authenticated connection.
503
- * @param connectionOpenCallback Be notified when a connection to the server is open.
504
- * @param connectionCloseCallback Be notified when a connection to the server is closed.
505
- * @param observer Be notified of hot module updates.
365
+ *
366
+ * @returns {HMRClient} the HMR Client
506
367
  */
507
- async function initializeClient(url, target, hotModuleHandler, authConfig, connectionOpenCallback, connectionCloseCallback, observer) {
508
- const clientId = generateRandomClientId();
509
- const connection = await getConnection(url, target, clientId, authConfig, connectionOpenCallback, connectionCloseCallback);
510
- const hmrClient = new HMRClient(clientId, connection, hotModuleHandler);
511
- if (observer) {
512
- hmrClient.notifyModuleUpdate.register(observer);
368
+ async function initializeClient(url, _target, hotModuleHandler, authConfig) {
369
+ const wsUrl = new URL(url);
370
+ wsUrl.searchParams.set('referer', location.href);
371
+ if (authConfig) {
372
+ wsUrl.searchParams.set('sessionTokenCipher', authConfig.authChallengeData.sessionTokenCipher);
373
+ }
374
+ const ws = await JSONWebSocket.init(wsUrl.toString(), 'lightning-dev-preview');
375
+ if (authConfig) {
376
+ try {
377
+ await waitForAuthResponse(ws, authConfig);
378
+ }
379
+ catch (e) {
380
+ ws.close();
381
+ throw e;
382
+ }
513
383
  }
384
+ const client = new HMRClient(ws, hotModuleHandler);
385
+ listenOnConsole(client);
386
+ return client;
387
+ }
388
+ function waitForAuthResponse(ws, authConfig) {
389
+ return new Promise((resolve, reject) => {
390
+ const listener = ({ data }) => {
391
+ if (!data)
392
+ reject(new Error('Invalid message, missing data.')); // TODO: log the reason here.
393
+ const payload = JSON.parse(data);
394
+ if (payload.type !== 'auth-response') {
395
+ logWarning(IGNORING_PREAUTH_RESPONSES); // TODO: log the reason here.
396
+ reject(new Error('Invalid non-auth response.'));
397
+ }
398
+ if (authConfig.authResponseCallback(payload.data)) {
399
+ resolve();
400
+ }
401
+ else {
402
+ reject(new Error(AUTH_FAILURE));
403
+ }
404
+ };
405
+ ws.socket.addEventListener('message', listener, { once: true });
406
+ });
407
+ }
408
+ /* eslint-disable no-console */
409
+ function listenOnConsole(hmrClient) {
410
+ hmrClient.addEventListener('hmr:create', (event) => console.log(`A new module was added at ${event.detail.modulePath}`));
411
+ hmrClient.addEventListener('hmr:update', (event) => console.log(`A source change detected at ${event.detail.modulePath}`));
412
+ hmrClient.addEventListener('hmr:hot-swapped', (event) => console.log(`Received a hot module with path ${event.detail.modulePath}, attempting to hot swap`));
413
+ hmrClient.addEventListener('hmr:delete', (event) => console.log(`A module was deleted at ${event.detail.modulePath}`));
514
414
  }
515
415
 
516
416
  exports.accept = accept;