"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.startTunnelsAsync = startTunnelsAsync; exports.stopTunnelsAsync = stopTunnelsAsync; function _config() { const data = require("@expo/config"); _config = function () { return data; }; return data; } function path() { const data = _interopRequireWildcard(require("path")); path = function () { return data; }; return data; } function _internal() { const data = require("../internal"); _internal = function () { return data; }; return data; } function UrlUtils() { const data = _interopRequireWildcard(require("./ngrokUrl")); UrlUtils = function () { return data; }; return data; } function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } const NGROK_CONFIG = { authToken: '5W1bR67GNbWcXqmxZzBG1_56GezNeaX6sSRvn8npeQ8', authTokenPublicId: '5W1bR67GNbWcXqmxZzBG1', domain: 'exp.direct' }; function getNgrokConfigPath() { return path().join(_internal().UserSettings.dotExpoHomeDirectory(), 'ngrok.yml'); } async function getProjectRandomnessAsync(projectRoot) { const ps = await _internal().ProjectSettings.readAsync(projectRoot); const randomness = ps.urlRandomness; if (randomness) { return randomness; } else { return resetProjectRandomnessAsync(projectRoot); } } async function resetProjectRandomnessAsync(projectRoot) { const randomness = UrlUtils().someRandomness(); _internal().ProjectSettings.setAsync(projectRoot, { urlRandomness: randomness }); return randomness; } async function connectToNgrokAsync(projectRoot, ngrok, args, hostnameAsync, ngrokPid, attempts = 0) { try { const configPath = getNgrokConfigPath(); const hostname = await hostnameAsync(); const url = await ngrok.connect({ hostname, configPath, onStatusChange: handleStatusChange.bind(null, projectRoot), ...args }); return url; } catch (e) { // Attempt to connect 3 times if (attempts >= 2) { if (e.message) { throw new (_internal().XDLError)('NGROK_ERROR', e.toString()); } else { throw new (_internal().XDLError)('NGROK_ERROR', JSON.stringify(e)); } } if (!attempts) { attempts = 0; } // Attempt to fix the issue if (e.error_code && e.error_code === 103) { if (attempts === 0) { // Failed to start tunnel. Might be because url already bound to another session. if (ngrokPid) { try { process.kill(ngrokPid, 'SIGKILL'); } catch { _internal().ProjectUtils.logDebug(projectRoot, 'expo', `Couldn't kill ngrok with PID ${ngrokPid}`); } } else { await ngrok.kill(); } } else { // Change randomness to avoid conflict if killing ngrok didn't help await resetProjectRandomnessAsync(projectRoot); } } // Wait 100ms and then try again await (0, _internal().delayAsync)(100); return connectToNgrokAsync(projectRoot, ngrok, args, hostnameAsync, null, attempts + 1); } } const TUNNEL_TIMEOUT = 10 * 1000; async function startTunnelsAsync(projectRoot, options = {}) { const ngrok = await (0, _internal().resolveNgrokAsync)(projectRoot, options); const username = (await _internal().UserManager.getCurrentUsernameAsync()) || _internal().ANONYMOUS_USERNAME; (0, _internal().assertValidProjectRoot)(projectRoot); const packagerInfo = await _internal().ProjectSettings.readPackagerInfoAsync(projectRoot); if (!packagerInfo.packagerPort) { throw new (_internal().XDLError)('NO_PACKAGER_PORT', `No packager found for project at ${projectRoot}.`); } if (!packagerInfo.expoServerPort) { throw new (_internal().XDLError)('NO_EXPO_SERVER_PORT', `No Expo server found for project at ${projectRoot}.`); } const expoServerPort = packagerInfo.expoServerPort; await stopTunnelsAsync(projectRoot); if (await _internal().Android.startAdbReverseAsync(projectRoot)) { _internal().ProjectUtils.logInfo(projectRoot, 'expo', 'Successfully ran `adb reverse`. Localhost URLs should work on the connected Android device.'); } const packageShortName = path().parse(projectRoot).base; const expRc = await (0, _config().readExpRcAsync)(projectRoot); let startedTunnelsSuccessfully = false; // Some issues with ngrok cause it to hang indefinitely. After // TUNNEL_TIMEOUTms we just throw an error. await Promise.race([(async () => { await (0, _internal().delayAsync)(TUNNEL_TIMEOUT); if (!startedTunnelsSuccessfully) { throw new Error('Starting tunnels timed out'); } })(), (async () => { const createResolver = (extra = []) => async function resolveHostnameAsync() { const randomness = expRc.manifestTunnelRandomness ? expRc.manifestTunnelRandomness : await getProjectRandomnessAsync(projectRoot); return [...extra, randomness, UrlUtils().domainify(username), UrlUtils().domainify(packageShortName), NGROK_CONFIG.domain].join('.'); }; // If both ports are defined and they don't match then we can assume the legacy dev server is being used. const isLegacyDevServer = !!expoServerPort && !!packagerInfo.packagerPort && expoServerPort !== packagerInfo.packagerPort; _internal().ProjectUtils.logInfo(projectRoot, 'expo', `Using legacy dev server: ${isLegacyDevServer}`); const expoServerNgrokUrl = await connectToNgrokAsync(projectRoot, ngrok, { authtoken: NGROK_CONFIG.authToken, port: expoServerPort, proto: 'http' }, createResolver(), packagerInfo.ngrokPid); let packagerNgrokUrl; if (isLegacyDevServer) { packagerNgrokUrl = await connectToNgrokAsync(projectRoot, ngrok, { authtoken: NGROK_CONFIG.authToken, port: packagerInfo.packagerPort, proto: 'http' }, createResolver(['packager']), packagerInfo.ngrokPid); } else { // Custom dev server will share the port across expo and metro dev servers, // this means we only need one ngrok URL. packagerNgrokUrl = expoServerNgrokUrl; } await _internal().ProjectSettings.setPackagerInfoAsync(projectRoot, { expoServerNgrokUrl, packagerNgrokUrl, ngrokPid: ngrok.getActiveProcess().pid }); startedTunnelsSuccessfully = true; _internal().ProjectUtils.logWithLevel(projectRoot, 'info', { tag: 'expo', _expoEventType: 'TUNNEL_READY' }, 'Tunnel ready.'); })()]); } async function stopTunnelsAsync(projectRoot) { (0, _internal().assertValidProjectRoot)(projectRoot); const ngrok = await (0, _internal().resolveNgrokAsync)(projectRoot, { shouldPrompt: false }).catch(() => null); if (!ngrok) { return; } // This will kill all ngrok tunnels in the process. // We'll need to change this if we ever support more than one project // open at a time in XDE. const packagerInfo = await _internal().ProjectSettings.readPackagerInfoAsync(projectRoot); const ngrokProcess = ngrok.getActiveProcess(); const ngrokProcessPid = ngrokProcess ? ngrokProcess.pid : null; if (packagerInfo.ngrokPid && packagerInfo.ngrokPid !== ngrokProcessPid) { // Ngrok is running in some other process. Kill at the os level. try { process.kill(packagerInfo.ngrokPid); } catch { _internal().ProjectUtils.logDebug(projectRoot, 'expo', `Couldn't kill ngrok with PID ${packagerInfo.ngrokPid}`); } } else { // Ngrok is running from the current process. Kill using ngrok api. await ngrok.kill(); } await _internal().ProjectSettings.setPackagerInfoAsync(projectRoot, { expoServerNgrokUrl: null, packagerNgrokUrl: null, ngrokPid: null }); await _internal().Android.stopAdbReverseAsync(projectRoot); } function handleStatusChange(projectRoot, status) { if (status === 'closed') { _internal().ProjectUtils.logError(projectRoot, 'expo', 'We noticed your tunnel is having issues. ' + 'This may be due to intermittent problems with our tunnel provider. ' + 'If you have trouble connecting to your app, try to Restart the project, ' + 'or switch Host to LAN.'); } else if (status === 'connected') { _internal().ProjectUtils.logInfo(projectRoot, 'expo', 'Tunnel connected.'); } } //# sourceMappingURL=ngrok.js.map