UNPKG

@expo/xdl

Version:
342 lines (267 loc) 10.2 kB
"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 _util() { const data = require("util"); _util = function () { return data; }; return data; } function Android() { const data = _interopRequireWildcard(require("../Android")); Android = function () { return data; }; return data; } function _Config() { const data = _interopRequireDefault(require("../Config")); _Config = function () { return data; }; return data; } function ProjectSettings() { const data = _interopRequireWildcard(require("../ProjectSettings")); ProjectSettings = function () { return data; }; return data; } function UrlUtils() { const data = _interopRequireWildcard(require("../UrlUtils")); UrlUtils = function () { return data; }; return data; } function _User() { const data = _interopRequireWildcard(require("../User")); _User = function () { return data; }; return data; } function _UserSettings() { const data = _interopRequireDefault(require("../UserSettings")); _UserSettings = function () { return data; }; return data; } function _XDLError() { const data = _interopRequireDefault(require("../XDLError")); _XDLError = function () { return data; }; return data; } function Logger() { const data = _interopRequireWildcard(require("../project/ProjectUtils")); Logger = function () { return data; }; return data; } function _errors() { const data = require("../project/errors"); _errors = function () { return data; }; return data; } function _delayAsync() { const data = require("../utils/delayAsync"); _delayAsync = function () { return data; }; return data; } function _resolveNgrok() { const data = require("./resolveNgrok"); _resolveNgrok = function () { return data; }; return data; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (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; } function getNgrokConfigPath() { return path().join(_UserSettings().default.dotExpoHomeDirectory(), 'ngrok.yml'); } async function getProjectRandomnessAsync(projectRoot) { const ps = await ProjectSettings().readAsync(projectRoot); const randomness = ps.urlRandomness; if (randomness) { return randomness; } else { return resetProjectRandomnessAsync(projectRoot); } } async function resetProjectRandomnessAsync(projectRoot) { const randomness = UrlUtils().someRandomness(); ProjectSettings().setAsync(projectRoot, { urlRandomness: randomness }); return randomness; } async function connectToNgrokAsync(projectRoot, ngrok, args, hostnameAsync, ngrokPid, attempts = 0) { const ngrokConnectAsync = (0, _util().promisify)(ngrok.connect); const ngrokKillAsync = (0, _util().promisify)(ngrok.kill); try { const configPath = getNgrokConfigPath(); const hostname = await hostnameAsync(); const url = await ngrokConnectAsync({ hostname, configPath, ...args }); return url; } catch (e) { // Attempt to connect 3 times if (attempts >= 2) { if (e.message) { throw new (_XDLError().default)('NGROK_ERROR', e.toString()); } else { throw new (_XDLError().default)('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 (e) { Logger().logDebug(projectRoot, 'expo', `Couldn't kill ngrok with PID ${ngrokPid}`); } } else { await ngrokKillAsync(); } } else { // Change randomness to avoid conflict if killing ngrok didn't help await resetProjectRandomnessAsync(projectRoot); } } // Wait 100ms and then try again await (0, _delayAsync().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, _resolveNgrok().resolveNgrokAsync)(projectRoot, options); const username = (await _User().default.getCurrentUsernameAsync()) || _User().ANONYMOUS_USERNAME; (0, _errors().assertValidProjectRoot)(projectRoot); const packagerInfo = await ProjectSettings().readPackagerInfoAsync(projectRoot); if (!packagerInfo.packagerPort) { throw new (_XDLError().default)('NO_PACKAGER_PORT', `No packager found for project at ${projectRoot}.`); } if (!packagerInfo.expoServerPort) { throw new (_XDLError().default)('NO_EXPO_SERVER_PORT', `No Expo server found for project at ${projectRoot}.`); } const expoServerPort = packagerInfo.expoServerPort; await stopTunnelsAsync(projectRoot); if (await Android().startAdbReverseAsync(projectRoot)) { Logger().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, _delayAsync().delayAsync)(TUNNEL_TIMEOUT); if (!startedTunnelsSuccessfully) { throw new Error('Starting tunnels timed out'); } })(), (async () => { const expoServerNgrokUrl = await connectToNgrokAsync(projectRoot, ngrok, { authtoken: _Config().default.ngrok.authToken, port: expoServerPort, proto: 'http' }, async () => { const randomness = expRc.manifestTunnelRandomness ? expRc.manifestTunnelRandomness : await getProjectRandomnessAsync(projectRoot); return [randomness, UrlUtils().domainify(username), UrlUtils().domainify(packageShortName), _Config().default.ngrok.domain].join('.'); }, packagerInfo.ngrokPid); const packagerNgrokUrl = await connectToNgrokAsync(projectRoot, ngrok, { authtoken: _Config().default.ngrok.authToken, port: packagerInfo.packagerPort, proto: 'http' }, async () => { const randomness = expRc.manifestTunnelRandomness ? expRc.manifestTunnelRandomness : await getProjectRandomnessAsync(projectRoot); return ['packager', randomness, UrlUtils().domainify(username), UrlUtils().domainify(packageShortName), _Config().default.ngrok.domain].join('.'); }, packagerInfo.ngrokPid); await ProjectSettings().setPackagerInfoAsync(projectRoot, { expoServerNgrokUrl, packagerNgrokUrl, ngrokPid: ngrok.process().pid }); startedTunnelsSuccessfully = true; Logger().logWithLevel(projectRoot, 'info', { tag: 'expo', _expoEventType: 'TUNNEL_READY' }, 'Tunnel ready.'); ngrok.addListener('statuschange', status => { if (status === 'reconnecting') { Logger().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 === 'online') { Logger().logInfo(projectRoot, 'expo', 'Tunnel connected.'); } }); })()]); } async function stopTunnelsAsync(projectRoot) { (0, _errors().assertValidProjectRoot)(projectRoot); const ngrok = await (0, _resolveNgrok().resolveNgrokAsync)(projectRoot, { shouldPrompt: false }).catch(() => null); if (!ngrok) { return; } const ngrokKillAsync = (0, _util().promisify)(ngrok.kill); // 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 ProjectSettings().readPackagerInfoAsync(projectRoot); const ngrokProcess = ngrok.process(); const ngrokProcessPid = ngrokProcess ? ngrokProcess.pid : null; ngrok.removeAllListeners('statuschange'); if (packagerInfo.ngrokPid && packagerInfo.ngrokPid !== ngrokProcessPid) { // Ngrok is running in some other process. Kill at the os level. try { process.kill(packagerInfo.ngrokPid); } catch (e) { Logger().logDebug(projectRoot, 'expo', `Couldn't kill ngrok with PID ${packagerInfo.ngrokPid}`); } } else { // Ngrok is running from the current process. Kill using ngrok api. await ngrokKillAsync(); } await ProjectSettings().setPackagerInfoAsync(projectRoot, { expoServerNgrokUrl: null, packagerNgrokUrl: null, ngrokPid: null }); await Android().stopAdbReverseAsync(projectRoot); } //# sourceMappingURL=../__sourcemaps__/start/ngrok.js.map