next
Version:
The React Framework
206 lines (205 loc) • 8.58 kB
JavaScript
import { useContext, useEffect } from 'react';
import { GlobalLayoutRouterContext } from '../../../../shared/lib/app-router-context.shared-runtime';
import { getSocketUrl } from '../get-socket-url';
import { HMR_MESSAGE_SENT_TO_BROWSER } from '../../../../server/dev/hot-reloader-types';
import { reportInvalidHmrMessage } from '../shared';
import { performFullReload, processMessage } from './hot-reloader-app';
import { logQueue } from '../../../../next-devtools/userspace/app/forward-logs';
import { InvariantError } from '../../../../shared/lib/invariant-error';
import { WEB_SOCKET_MAX_RECONNECTIONS } from '../../../../lib/constants';
let reconnections = 0;
let reloading = false;
let serverSessionId = null;
let mostRecentCompilationHash = null;
export function createWebSocket(assetPrefix, staticIndicatorState) {
if (!self.__next_r) {
throw Object.defineProperty(new InvariantError(`Expected a request ID to be defined for the document via self.__next_r.`), "__NEXT_ERROR_CODE", {
value: "E806",
enumerable: false,
configurable: true
});
}
let webSocket;
let timer;
const sendMessage = (data)=>{
if (webSocket && webSocket.readyState === webSocket.OPEN) {
webSocket.send(data);
}
};
const processTurbopackMessage = createProcessTurbopackMessage(sendMessage);
function init() {
if (webSocket) {
webSocket.close();
}
const newWebSocket = new window.WebSocket(`${getSocketUrl(assetPrefix)}/_next/webpack-hmr?id=${self.__next_r}`);
newWebSocket.binaryType = 'arraybuffer';
function handleOnline() {
logQueue.onSocketReady(newWebSocket);
reconnections = 0;
window.console.log('[HMR] connected');
}
function handleMessage(event) {
// While the page is reloading, don't respond to any more messages.
if (reloading) {
return;
}
try {
const message = event.data instanceof ArrayBuffer ? parseBinaryMessage(event.data) : JSON.parse(event.data);
// Check for server restart in Turbopack mode
if (message.type === HMR_MESSAGE_SENT_TO_BROWSER.TURBOPACK_CONNECTED) {
if (serverSessionId !== null && serverSessionId !== message.data.sessionId) {
// Either the server's session id has changed and it's a new server, or
// it's been too long since we disconnected and we should reload the page.
window.location.reload();
reloading = true;
return;
}
serverSessionId = message.data.sessionId;
}
// Track webpack compilation hash for server restart detection
if (message.type === HMR_MESSAGE_SENT_TO_BROWSER.SYNC && 'hash' in message) {
// If we had previously reconnected and the hash changed, the server may have restarted
if (mostRecentCompilationHash !== null && mostRecentCompilationHash !== message.hash) {
window.location.reload();
reloading = true;
return;
}
mostRecentCompilationHash = message.hash;
}
processMessage(message, sendMessage, processTurbopackMessage, staticIndicatorState);
} catch (err) {
reportInvalidHmrMessage(event, err);
}
}
function handleDisconnect() {
newWebSocket.onerror = null;
newWebSocket.onclose = null;
newWebSocket.close();
reconnections++;
// After 25 reconnects we'll want to reload the page as it indicates the dev server is no longer running.
if (reconnections > WEB_SOCKET_MAX_RECONNECTIONS) {
reloading = true;
window.location.reload();
return;
}
clearTimeout(timer);
// Try again after 5 seconds
timer = setTimeout(init, reconnections > 5 ? 5000 : 1000);
}
newWebSocket.onopen = handleOnline;
newWebSocket.onerror = handleDisconnect;
newWebSocket.onclose = handleDisconnect;
newWebSocket.onmessage = handleMessage;
webSocket = newWebSocket;
return newWebSocket;
}
return init();
}
export function createProcessTurbopackMessage(sendMessage) {
if (!process.env.TURBOPACK) {
return ()=>{};
}
let queue = [];
let callback;
const processTurbopackMessage = (msg)=>{
if (callback) {
callback(msg);
} else {
queue.push(msg);
}
};
import(// @ts-expect-error requires "moduleResolution": "node16" in tsconfig.json and not .ts extension
'@vercel/turbopack-ecmascript-runtime/browser/dev/hmr-client/hmr-client.ts').then(({ connect })=>{
connect({
addMessageListener (cb) {
callback = cb;
// Replay all Turbopack messages before we were able to establish the HMR client.
for (const msg of queue){
cb(msg);
}
queue.length = 0;
},
sendMessage,
onUpdateError: (err)=>performFullReload(err, sendMessage)
});
});
return processTurbopackMessage;
}
export function useWebSocketPing(webSocket) {
const { tree } = useContext(GlobalLayoutRouterContext);
useEffect(()=>{
if (!webSocket) {
throw Object.defineProperty(new InvariantError('Expected webSocket to be defined in dev mode.'), "__NEXT_ERROR_CODE", {
value: "E785",
enumerable: false,
configurable: true
});
}
// Never send pings when using Turbopack as it's not used.
// Pings were originally used to keep track of active routes in on-demand-entries with webpack.
if (process.env.TURBOPACK) {
return;
}
// Taken from on-demand-entries-client.js
const interval = setInterval(()=>{
if (webSocket.readyState === webSocket.OPEN) {
webSocket.send(JSON.stringify({
event: 'ping',
tree,
appDirRoute: true
}));
}
}, 2500);
return ()=>clearInterval(interval);
}, [
tree,
webSocket
]);
}
const textDecoder = new TextDecoder();
function parseBinaryMessage(data) {
assertByteLength(data, 1);
const view = new DataView(data);
const messageType = view.getUint8(0);
switch(messageType){
case HMR_MESSAGE_SENT_TO_BROWSER.ERRORS_TO_SHOW_IN_BROWSER:
{
const serializedErrors = new Uint8Array(data, 1);
return {
type: HMR_MESSAGE_SENT_TO_BROWSER.ERRORS_TO_SHOW_IN_BROWSER,
serializedErrors
};
}
case HMR_MESSAGE_SENT_TO_BROWSER.REACT_DEBUG_CHUNK:
{
assertByteLength(data, 2);
const requestIdLength = view.getUint8(1);
assertByteLength(data, 2 + requestIdLength);
const requestId = textDecoder.decode(new Uint8Array(data, 2, requestIdLength));
const chunk = data.byteLength > 2 + requestIdLength ? new Uint8Array(data, 2 + requestIdLength) : null;
return {
type: HMR_MESSAGE_SENT_TO_BROWSER.REACT_DEBUG_CHUNK,
requestId,
chunk
};
}
default:
{
throw Object.defineProperty(new InvariantError(`Invalid binary HMR message of type ${messageType}`), "__NEXT_ERROR_CODE", {
value: "E809",
enumerable: false,
configurable: true
});
}
}
}
function assertByteLength(data, expectedLength) {
if (data.byteLength < expectedLength) {
throw Object.defineProperty(new InvariantError(`Invalid binary HMR message: insufficient data (expected ${expectedLength} bytes, got ${data.byteLength})`), "__NEXT_ERROR_CODE", {
value: "E808",
enumerable: false,
configurable: true
});
}
}
//# sourceMappingURL=web-socket.js.map