UNPKG

@expo/cli

Version:
263 lines (262 loc) 11 kB
"use strict"; 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