@expo/xdl
Version:
The Expo Development Library
342 lines (267 loc) • 10.2 kB
JavaScript
;
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