1"use strict"; 2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 if (k2 === undefined) k2 = k; 4 var desc = Object.getOwnPropertyDescriptor(m, k); 5 if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 desc = { enumerable: true, get: function() { return m[k]; } }; 7 } 8 Object.defineProperty(o, k2, desc); 9}) : (function(o, m, k, k2) { 10 if (k2 === undefined) k2 = k; 11 o[k2] = m[k]; 12})); 13var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 14 Object.defineProperty(o, "default", { enumerable: true, value: v }); 15}) : function(o, v) { 16 o["default"] = v; 17}); 18var __importStar = (this && this.__importStar) || function (mod) { 19 if (mod && mod.__esModule) return mod; 20 var result = {}; 21 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 __setModuleDefault(result, mod); 23 return result; 24}; 25var __importDefault = (this && this.__importDefault) || function (mod) { 26 return (mod && mod.__esModule) ? mod : { "default": mod }; 27}; 28Object.defineProperty(exports, "__esModule", { value: true }); 29/** 30 * Copyright (c) 650 Industries. 31 * Copyright (c) Meta Platforms, Inc. and affiliates. 32 * 33 * This source code is licensed under the MIT license found in the 34 * LICENSE file in the root directory of this source tree. 35 * 36 * Based on this but with web support: 37 * https://github.com/facebook/react-native/blob/086714b02b0fb838dee5a66c5bcefe73b53cf3df/Libraries/Utilities/HMRClient.js 38 */ 39const pretty_format_1 = __importStar(require("pretty-format")); 40const LoadingView_1 = __importDefault(require("./LoadingView")); 41const LogBox_1 = __importDefault(require("./error-overlay/LogBox")); 42const getDevServer_1 = __importDefault(require("./getDevServer")); 43const MetroHMRClient = require('metro-runtime/src/modules/HMRClient'); 44const pendingEntryPoints = []; 45let hmrClient = null; 46let hmrUnavailableReason = null; 47let currentCompileErrorMessage = null; 48let didConnect = false; 49const pendingLogs = []; 50function assert(foo, msg) { 51 if (!foo) 52 throw new Error(msg); 53} 54/** 55 * HMR Client that receives from the server HMR updates and propagates them 56 * runtime to reflects those changes. 57 */ 58const HMRClient = { 59 enable() { 60 if (hmrUnavailableReason !== null) { 61 // If HMR became unavailable while you weren't using it, 62 // explain why when you try to turn it on. 63 // This is an error (and not a warning) because it is shown 64 // in response to a direct user action. 65 throw new Error(hmrUnavailableReason); 66 } 67 assert(hmrClient, 'Expected HMRClient.setup() call at startup.'); 68 // We use this for internal logging only. 69 // It doesn't affect the logic. 70 hmrClient.send(JSON.stringify({ type: 'log-opt-in' })); 71 // When toggling Fast Refresh on, we might already have some stashed updates. 72 // Since they'll get applied now, we'll show a banner. 73 const hasUpdates = hmrClient.hasPendingUpdates(); 74 if (hasUpdates) { 75 LoadingView_1.default.showMessage('Refreshing...', 'refresh'); 76 } 77 try { 78 hmrClient.enable(); 79 } 80 finally { 81 if (hasUpdates) { 82 LoadingView_1.default.hide(); 83 } 84 } 85 // There could be a compile error while Fast Refresh was off, 86 // but we ignored it at the time. Show it now. 87 showCompileError(); 88 }, 89 disable() { 90 assert(hmrClient, 'Expected HMRClient.setup() call at startup.'); 91 hmrClient.disable(); 92 }, 93 registerBundle(requestUrl) { 94 assert(hmrClient, 'Expected HMRClient.setup() call at startup.'); 95 pendingEntryPoints.push(requestUrl); 96 registerBundleEntryPoints(hmrClient); 97 }, 98 log(level, data) { 99 if (!hmrClient) { 100 // Catch a reasonable number of early logs 101 // in case hmrClient gets initialized later. 102 pendingLogs.push([level, data]); 103 if (pendingLogs.length > 100) { 104 pendingLogs.shift(); 105 } 106 return; 107 } 108 try { 109 hmrClient.send(JSON.stringify({ 110 type: 'log', 111 level, 112 mode: 'BRIDGE', 113 data: data.map((item) => typeof item === 'string' 114 ? item 115 : (0, pretty_format_1.default)(item, { 116 escapeString: true, 117 highlight: true, 118 maxDepth: 3, 119 min: true, 120 plugins: [pretty_format_1.plugins.ReactElement], 121 })), 122 })); 123 } 124 catch { 125 // If sending logs causes any failures we want to silently ignore them 126 // to ensure we do not cause infinite-logging loops. 127 } 128 }, 129 // Called once by the bridge on startup, even if Fast Refresh is off. 130 // It creates the HMR client but doesn't actually set up the socket yet. 131 setup({ isEnabled }) { 132 assert(!hmrClient, 'Cannot initialize hmrClient twice'); 133 const serverScheme = window.location.protocol === 'https:' ? 'wss' : 'ws'; 134 const client = new MetroHMRClient(`${serverScheme}://${window.location.host}/hot`); 135 hmrClient = client; 136 const { fullBundleUrl } = (0, getDevServer_1.default)(); 137 pendingEntryPoints.push( 138 // HMRServer understands regular bundle URLs, so prefer that in case 139 // there are any important URL parameters we can't reconstruct from 140 // `setup()`'s arguments. 141 fullBundleUrl); 142 client.on('connection-error', (e) => { 143 let error = `Cannot connect to Metro. 144 145 Try the following to fix the issue: 146 - Ensure the Metro dev server is running and available on the same network as this device`; 147 error += ` 148 149 URL: ${window.location.host} 150 151 Error: ${e.message}`; 152 setHMRUnavailableReason(error); 153 }); 154 client.on('update-start', ({ isInitialUpdate }) => { 155 currentCompileErrorMessage = null; 156 didConnect = true; 157 if (client.isEnabled() && !isInitialUpdate) { 158 LoadingView_1.default.showMessage('Refreshing...', 'refresh'); 159 } 160 }); 161 client.on('update', ({ isInitialUpdate }) => { 162 if (client.isEnabled() && !isInitialUpdate) { 163 dismissRedbox(); 164 LogBox_1.default.clearAllLogs(); 165 } 166 }); 167 client.on('update-done', () => { 168 LoadingView_1.default.hide(); 169 }); 170 client.on('error', (data) => { 171 LoadingView_1.default.hide(); 172 if (data.type === 'GraphNotFoundError') { 173 client.close(); 174 setHMRUnavailableReason('Metro has restarted since the last edit. Reload to reconnect.'); 175 } 176 else if (data.type === 'RevisionNotFoundError') { 177 client.close(); 178 setHMRUnavailableReason('Metro and the client are out of sync. Reload to reconnect.'); 179 } 180 else { 181 currentCompileErrorMessage = `${data.type} ${data.message}`; 182 if (client.isEnabled()) { 183 showCompileError(); 184 } 185 } 186 }); 187 client.on('close', (closeEvent) => { 188 LoadingView_1.default.hide(); 189 // https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1 190 // https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.5 191 const isNormalOrUnsetCloseReason = closeEvent == null || 192 closeEvent.code === 1000 || 193 closeEvent.code === 1005 || 194 closeEvent.code == null; 195 setHMRUnavailableReason(`${isNormalOrUnsetCloseReason 196 ? 'Disconnected from Metro.' 197 : `Disconnected from Metro (${closeEvent.code}: "${closeEvent.reason}").`} 198 199To reconnect: 200- Ensure that Metro is running and available on the same network 201- Reload this app (will trigger further help if Metro cannot be connected to) 202 `); 203 }); 204 if (isEnabled) { 205 HMRClient.enable(); 206 } 207 else { 208 HMRClient.disable(); 209 } 210 registerBundleEntryPoints(hmrClient); 211 flushEarlyLogs(); 212 }, 213}; 214function setHMRUnavailableReason(reason) { 215 assert(hmrClient, 'Expected HMRClient.setup() call at startup.'); 216 if (hmrUnavailableReason !== null) { 217 // Don't show more than one warning. 218 return; 219 } 220 hmrUnavailableReason = reason; 221 // We only want to show a warning if Fast Refresh is on *and* if we ever 222 // previously managed to connect successfully. We don't want to show 223 // the warning to native engineers who use cached bundles without Metro. 224 if (hmrClient.isEnabled() && didConnect) { 225 console.warn(reason); 226 // (Not using the `warning` module to prevent a Buck cycle.) 227 } 228} 229function registerBundleEntryPoints(client) { 230 if (hmrUnavailableReason != null) { 231 // "Bundle Splitting – Metro disconnected" 232 window.location.reload(); 233 return; 234 } 235 if (pendingEntryPoints.length > 0) { 236 client?.send(JSON.stringify({ 237 type: 'register-entrypoints', 238 entryPoints: pendingEntryPoints, 239 })); 240 pendingEntryPoints.length = 0; 241 } 242} 243function flushEarlyLogs() { 244 try { 245 pendingLogs.forEach(([level, data]) => { 246 HMRClient.log(level, data); 247 }); 248 } 249 finally { 250 pendingLogs.length = 0; 251 } 252} 253function dismissRedbox() { 254 // TODO(EvanBacon): Error overlay for web. 255} 256function showCompileError() { 257 if (currentCompileErrorMessage === null) { 258 return; 259 } 260 // Even if there is already a redbox, syntax errors are more important. 261 // Otherwise you risk seeing a stale runtime error while a syntax error is more recent. 262 dismissRedbox(); 263 const message = currentCompileErrorMessage; 264 currentCompileErrorMessage = null; 265 const error = new Error(message); 266 // Symbolicating compile errors is wasted effort 267 // because the stack trace is meaningless: 268 // @ts-expect-error 269 error.preventSymbolication = true; 270 throw error; 271} 272exports.default = HMRClient; 273//# sourceMappingURL=HMRClient.js.map