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