You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

212 lines
9.1 KiB

"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