@expo/cli
Version:
263 lines (262 loc) • 11 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "AsyncNgrok", {
enumerable: true,
get: ()=>AsyncNgrok
});
function _chalk() {
const data = /*#__PURE__*/ _interopRequireDefault(require("chalk"));
_chalk = function() {
return data;
};
return data;
}
function _crypto() {
const data = /*#__PURE__*/ _interopRequireDefault(require("crypto"));
_crypto = function() {
return data;
};
return data;
}
function _path() {
const data = /*#__PURE__*/ _interopRequireWildcard(require("path"));
_path = function() {
return data;
};
return data;
}
function _slugify() {
const data = /*#__PURE__*/ _interopRequireDefault(require("slugify"));
_slugify = function() {
return data;
};
return data;
}
const _userSettings = /*#__PURE__*/ _interopRequireDefault(require("../../api/user/UserSettings"));
const _user = require("../../api/user/user");
const _log = /*#__PURE__*/ _interopRequireWildcard(require("../../log"));
const _delay = require("../../utils/delay");
const _env = require("../../utils/env");
const _errors = require("../../utils/errors");
const _ngrokResolver = require("../doctor/ngrok/NgrokResolver");
const _adbReverse = require("../platforms/android/adbReverse");
const _settings = require("../project/settings");
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
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 debug = require("debug")("expo:start:server:ngrok");
const NGROK_CONFIG = {
authToken: "5W1bR67GNbWcXqmxZzBG1_56GezNeaX6sSRvn8npeQ8",
domain: "exp.direct"
};
const TUNNEL_TIMEOUT = 10 * 1000;
class AsyncNgrok {
constructor(projectRoot, port){
this.projectRoot = projectRoot;
this.port = port;
this.serverUrl = null;
this.resolver = new _ngrokResolver.NgrokResolver(projectRoot);
}
getActiveUrl() {
return this.serverUrl;
}
/** Exposed for testing. */ async _getIdentifyingUrlSegmentsAsync() {
const user = await (0, _user.getUserAsync)();
if ((user == null ? void 0 : user.__typename) === "Robot") {
throw new _errors.CommandError("NGROK_ROBOT", "Cannot use ngrok with a robot user.");
}
const username = (0, _user.getActorDisplayName)(user);
return [
// NOTE: https://github.com/expo/expo/pull/16556#discussion_r822944286
await this.getProjectRandomnessAsync(),
// Strip out periods from the username to avoid subdomain issues with SSL certificates.
(0, _slugify().default)(username, {
remove: /\./
}),
// Use the port to distinguish between multiple tunnels (webpack, metro).
String(this.port),
];
}
/** Exposed for testing. */ async _getProjectHostnameAsync() {
return `${(await this._getIdentifyingUrlSegmentsAsync()).join("-")}.${NGROK_CONFIG.domain}`;
}
/** Exposed for testing. */ async _getProjectSubdomainAsync() {
return (await this._getIdentifyingUrlSegmentsAsync()).join("-");
}
/** Start ngrok on the given port for the project. */ async startAsync({ timeout } = {}) {
// Ensure the instance is loaded first, this can linger so we should run it before the timeout.
await this.resolver.resolveAsync({
// For now, prefer global install since the package has native code (harder to install) and doesn't change very often.
prefersGlobalInstall: true
});
// NOTE(EvanBacon): If the user doesn't have ADB installed,
// then skip attempting to reverse the port.
if ((0, _adbReverse.hasAdbReverseAsync)()) {
// Ensure ADB reverse is running.
if (!await (0, _adbReverse.startAdbReverseAsync)([
this.port
])) {
// TODO: Better error message.
throw new _errors.CommandError("NGROK_ADB", `Cannot start tunnel URL because \`adb reverse\` failed for the connected Android device(s).`);
}
}
this.serverUrl = await this._connectToNgrokAsync({
timeout
});
debug("Tunnel URL:", this.serverUrl);
_log.log("Tunnel ready.");
}
/** Stop the ngrok process if it's running. */ async stopAsync() {
var ref;
debug("Stopping Tunnel");
await ((ref = this.resolver.get()) == null ? void 0 : ref.kill == null ? void 0 : ref.kill());
this.serverUrl = null;
}
/** Exposed for testing. */ async _connectToNgrokAsync(options = {}, attempts = 0) {
// Attempt to stop any hanging processes, this increases the chances of a successful connection.
await this.stopAsync();
// Get the instance quietly or assert otherwise.
const instance = await this.resolver.resolveAsync({
shouldPrompt: false,
autoInstall: false
});
var _timeout;
// TODO(Bacon): Consider dropping the timeout functionality:
// https://github.com/expo/expo/pull/16556#discussion_r822307373
const results = await (0, _delay.resolveWithTimeout)(()=>this.connectToNgrokInternalAsync(instance, attempts), {
timeout: (_timeout = options.timeout) != null ? _timeout : TUNNEL_TIMEOUT,
errorMessage: "ngrok tunnel took too long to connect."
});
if (typeof results === "string") {
return results;
}
// Wait 100ms and then try again
await (0, _delay.delayAsync)(100);
return this._connectToNgrokAsync(options, attempts + 1);
}
async _getConnectionPropsAsync() {
const userDefinedSubdomain = _env.env.EXPO_TUNNEL_SUBDOMAIN;
if (userDefinedSubdomain) {
const subdomain = typeof userDefinedSubdomain === "string" ? userDefinedSubdomain : await this._getProjectSubdomainAsync();
debug("Subdomain:", subdomain);
return {
subdomain
};
} else {
const hostname = await this._getProjectHostnameAsync();
debug("Hostname:", hostname);
return {
hostname
};
}
}
async connectToNgrokInternalAsync(instance, attempts = 0) {
try {
// Global config path.
const configPath = _path().join(_userSettings.default.getDirectory(), "ngrok.yml");
debug("Global config path:", configPath);
const urlProps = await this._getConnectionPropsAsync();
const url = await instance.connect({
...urlProps,
authtoken: NGROK_CONFIG.authToken,
configPath,
onStatusChange (status) {
if (status === "closed") {
_log.error(_chalk().default.red("Tunnel connection has been closed. This is often related to intermittent connection problems with the Ngrok servers. Restart the dev server to try connecting to Ngrok again.") + _chalk().default.gray("\nCheck the Ngrok status page for outages: https://status.ngrok.com/"));
} else if (status === "connected") {
_log.log("Tunnel connected.");
}
},
port: this.port
});
return url;
} catch (error) {
const assertNgrok = ()=>{
if ((0, _ngrokResolver.isNgrokClientError)(error)) {
var ref;
throw new _errors.CommandError("NGROK_CONNECT", [
error.body.msg,
(ref = error.body.details) == null ? void 0 : ref.err,
_chalk().default.gray("Check the Ngrok status page for outages: https://status.ngrok.com/"),
].filter(Boolean).join("\n\n"));
}
throw new _errors.CommandError("NGROK_CONNECT", error.toString() + _chalk().default.gray("\nCheck the Ngrok status page for outages: https://status.ngrok.com/"));
};
// Attempt to connect 3 times
if (attempts >= 2) {
assertNgrok();
}
// Attempt to fix the issue
if ((0, _ngrokResolver.isNgrokClientError)(error) && error.body.error_code === 103) {
// Assert early if a custom subdomain is used since it cannot
// be changed and retried. If the tunnel subdomain is a boolean
// then we can reset the randomness and try again.
if (typeof _env.env.EXPO_TUNNEL_SUBDOMAIN === "string") {
assertNgrok();
}
// Change randomness to avoid conflict if killing ngrok doesn't help
await this._resetProjectRandomnessAsync();
}
return false;
}
}
async getProjectRandomnessAsync() {
const { urlRandomness: randomness } = await _settings.ProjectSettings.readAsync(this.projectRoot);
if (randomness) {
return randomness;
}
return await this._resetProjectRandomnessAsync();
}
async _resetProjectRandomnessAsync() {
const randomness = _crypto().default.randomBytes(5).toString("base64url");
await _settings.ProjectSettings.setAsync(this.projectRoot, {
urlRandomness: randomness
});
debug("Resetting project randomness:", randomness);
return randomness;
}
}
//# sourceMappingURL=AsyncNgrok.js.map
;