"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getBundleUrlAsync = getBundleUrlAsync; exports.getExpoGoConfig = getExpoGoConfig; exports.getManifestHandler = getManifestHandler; exports.getManifestResponseAsync = getManifestResponseAsync; exports.getPackagerOptionsAsync = getPackagerOptionsAsync; exports.getSignedManifestStringAsync = getSignedManifestStringAsync; exports.getUnsignedManifestString = getUnsignedManifestString; exports.stripPort = stripPort; function _config() { const data = require("@expo/config"); _config = function () { return data; }; return data; } function _chalk() { const data = _interopRequireDefault(require("chalk")); _chalk = function () { return data; }; return data; } function _os() { const data = _interopRequireDefault(require("os")); _os = function () { return data; }; return data; } function _url() { const data = require("url"); _url = function () { return data; }; return data; } function _internal() { const data = require("../internal"); _internal = function () { return data; }; return data; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const _cachedSignedManifest = { manifestString: null, signedManifest: null }; const blacklistedEnvironmentVariables = new Set(['EXPO_APPLE_PASSWORD', 'EXPO_ANDROID_KEY_PASSWORD', 'EXPO_ANDROID_KEYSTORE_PASSWORD', 'EXPO_IOS_DIST_P12_PASSWORD', 'EXPO_IOS_PUSH_P12_PASSWORD', 'EXPO_CLI_PASSWORD']); function shouldExposeEnvironmentVariableInManifest(key) { if (blacklistedEnvironmentVariables.has(key.toUpperCase())) { return false; } return key.startsWith('REACT_NATIVE_') || key.startsWith('EXPO_'); } function stripPort(host) { if (!host) { return host; } return new (_url().URL)('/', `http://${host}`).hostname; } async function getPackagerOptionsAsync(projectRoot) { // Get packager opts and then copy into bundleUrlPackagerOpts const projectSettings = await _internal().ProjectSettings.readAsync(projectRoot); const bundleUrlPackagerOpts = JSON.parse(JSON.stringify(projectSettings)); bundleUrlPackagerOpts.urlType = 'http'; if (bundleUrlPackagerOpts.hostType === 'redirect') { bundleUrlPackagerOpts.hostType = 'tunnel'; } return [projectSettings, bundleUrlPackagerOpts]; } async function getBundleUrlAsync({ projectRoot, platform, projectSettings, bundleUrlPackagerOpts, mainModuleName, hostname }) { const queryParams = _internal().UrlUtils.constructBundleQueryParams(projectRoot, projectSettings); const path = `/${encodeURI(mainModuleName)}.bundle?platform=${encodeURIComponent(platform)}&${queryParams}`; return (await _internal().UrlUtils.constructBundleUrlAsync(projectRoot, bundleUrlPackagerOpts, hostname)) + path; } function getPlatformFromRequest(headers) { return (headers['exponent-platform'] || 'ios').toString(); } function getManifestHandler(projectRoot) { return async (req, res, next) => { // Only support `/`, `/manifest`, `/index.exp` for the manifest middleware. if (!req.url || !['/', '/manifest', '/index.exp'].includes( // Strip the query params (0, _url().parse)(req.url).pathname || req.url)) { next(); return; } try { var _exp$sdkVersion; // We intentionally don't `await`. We want to continue trying even // if there is a potential error in the package.json and don't want to slow // down the request _internal().Doctor.validateWithNetworkAsync(projectRoot).catch(error => { _internal().ProjectUtils.logError(projectRoot, 'expo', `Error: could not load config json at ${projectRoot}: ${error.toString()}`, 'doctor-config-json-not-read'); }); const { manifestString, exp, hostInfo } = await getManifestResponseFromHeadersAsync({ projectRoot, headers: req.headers }); const sdkVersion = (_exp$sdkVersion = exp.sdkVersion) !== null && _exp$sdkVersion !== void 0 ? _exp$sdkVersion : null; // Send the response res.setHeader('Exponent-Server', JSON.stringify(hostInfo)); // End the request res.end(manifestString); // Log analytics _internal().Analytics.logEvent('Serve Manifest', { developerTool: _internal().Config.developerTool, sdkVersion }); } catch (e) { _internal().ProjectUtils.logError(projectRoot, 'expo', e.stack); // 5xx = Server Error HTTP code res.statusCode = 520; res.end(JSON.stringify({ error: e.toString() })); } try { const deviceIds = req.headers['expo-dev-client-id']; if (deviceIds) { await _internal().ProjectSettings.saveDevicesAsync(projectRoot, deviceIds); } } catch (e) { _internal().ProjectUtils.logError(projectRoot, 'expo', e.stack); } }; } async function getManifestResponseFromHeadersAsync({ projectRoot, headers }) { // Read from headers const platform = getPlatformFromRequest(headers); const acceptSignature = headers['exponent-accept-signature']; return getManifestResponseAsync({ projectRoot, host: headers.host, platform, acceptSignature }); } async function getExpoGoConfig({ projectRoot, projectSettings, mainModuleName, hostname }) { const [debuggerHost, logUrl] = await Promise.all([_internal().UrlUtils.constructDebuggerHostAsync(projectRoot, hostname), _internal().UrlUtils.constructLogUrlAsync(projectRoot, hostname)]); return { developer: { tool: _internal().Config.developerTool, projectRoot }, packagerOpts: projectSettings, mainModuleName, // Add this string to make Flipper register React Native / Metro as "running". // Can be tested by running: // `METRO_SERVER_PORT=19000 open -a flipper.app` // Where 19000 is the port where the Expo project is being hosted. __flipperHack: 'React Native packager is running', debuggerHost, logUrl }; } async function getManifestResponseAsync({ projectRoot, host, platform, acceptSignature }) { // Read the config const projectConfig = (0, _config().getConfig)(projectRoot, { skipSDKVersionRequirement: true }); // Opt towards newest functionality when expo isn't installed. if (!projectConfig.exp.sdkVersion) { projectConfig.exp.sdkVersion = 'UNVERSIONED'; } // Read from headers const hostname = stripPort(host); // Get project entry point and initial module let entryPoint = (0, _internal().resolveEntryPoint)(projectRoot, platform, projectConfig); // NOTE(Bacon): Webpack is currently hardcoded to index.bundle on native // in the future (TODO) we should move this logic into a Webpack plugin and use // a generated file name like we do on web. if (_internal().Webpack.isTargetingNative()) { entryPoint = 'index.js'; } const mainModuleName = _internal().UrlUtils.stripJSExtension(entryPoint); // Gather packager and host info const hostInfo = await createHostInfoAsync(); const [projectSettings, bundleUrlPackagerOpts] = await getPackagerOptionsAsync(projectRoot); // Create the manifest and set fields within it const expoGoConfig = await getExpoGoConfig({ projectRoot, projectSettings, mainModuleName, hostname }); const hostUri = await _internal().UrlUtils.constructHostUriAsync(projectRoot, hostname); const manifest = { ...projectConfig.exp, ...expoGoConfig, hostUri }; // Adding the env variables to the Expo manifest is unsafe. // This feature is deprecated in SDK 41 forward. if (manifest.sdkVersion && _internal().Versions.lteSdkVersion(manifest, '40.0.0')) { manifest.env = getManifestEnvironment(); } // Add URLs to the manifest manifest.bundleUrl = await getBundleUrlAsync({ projectRoot, platform, projectSettings, bundleUrlPackagerOpts, mainModuleName, hostname }); // Resolve all assets and set them on the manifest as URLs await _internal().ProjectAssets.resolveManifestAssets({ projectRoot, manifest, async resolver(path) { if (_internal().Webpack.isTargetingNative()) { // When using our custom dev server, just do assets normally // without the `assets/` subpath redirect. return (0, _url().resolve)(manifest.bundleUrl.match(/^https?:\/\/.*?\//)[0], path); } return manifest.bundleUrl.match(/^https?:\/\/.*?\//)[0] + 'assets/' + path; } }); // The server normally inserts this but if we're offline we'll do it here await _internal().ProjectAssets.resolveGoogleServicesFile(projectRoot, manifest); // Create the final string let manifestString; try { manifestString = await getManifestStringAsync(manifest, hostInfo.host, acceptSignature); } catch (error) { if (error.code === 'UNAUTHORIZED_ERROR' && manifest.owner) { // Don't have permissions for siging, warn and enable offline mode. addSigningDisabledWarning(projectRoot, `This project belongs to ${_chalk().default.bold(`@${manifest.owner}`)} and you have not been granted the appropriate permissions.\n` + `Please request access from an admin of @${manifest.owner} or change the "owner" field to an account you belong to.\n` + (0, _internal().learnMore)('https://docs.expo.dev/versions/latest/config/app/#owner')); _internal().ConnectionStatus.setIsOffline(true); manifestString = await getManifestStringAsync(manifest, hostInfo.host, acceptSignature); } else if (error.code === 'ENOTFOUND') { // Got a DNS error, i.e. can't access exp.host, warn and enable offline mode. addSigningDisabledWarning(projectRoot, `Could not reach Expo servers, please check if you can access ${error.hostname || 'exp.host'}.`); _internal().ConnectionStatus.setIsOffline(true); manifestString = await getManifestStringAsync(manifest, hostInfo.host, acceptSignature); } else { throw error; } } return { manifestString, exp: manifest, hostInfo }; } const addSigningDisabledWarning = (() => { let seen = false; return (projectRoot, reason) => { if (!seen) { seen = true; _internal().ProjectUtils.logWarning(projectRoot, 'expo', `${reason}\nFalling back to offline mode.`, 'signing-disabled'); } }; })(); function getManifestEnvironment() { return Object.keys(process.env).reduce((prev, key) => { if (shouldExposeEnvironmentVariableInManifest(key)) { prev[key] = process.env[key]; } return prev; }, {}); } async function getManifestStringAsync(manifest, hostUUID, acceptSignature) { const currentSession = await _internal().UserManager.getSessionAsync(); if (!currentSession || _internal().ConnectionStatus.isOffline()) { manifest.id = `@${_internal().ANONYMOUS_USERNAME}/${manifest.slug}-${hostUUID}`; } if (!acceptSignature) { return JSON.stringify(manifest); } else if (!currentSession || _internal().ConnectionStatus.isOffline()) { return getUnsignedManifestString(manifest); } else { return await getSignedManifestStringAsync(manifest, currentSession); } } async function createHostInfoAsync() { const host = await _internal().UserSettings.getAnonymousIdentifierAsync(); return { host, server: 'xdl', serverVersion: require('xdl/package.json').version, serverDriver: _internal().Config.developerTool, serverOS: _os().default.platform(), serverOSVersion: _os().default.release() }; } async function getSignedManifestStringAsync(manifest, // NOTE: we currently ignore the currentSession that is passed in, see the note below about analytics. currentSession) { var _manifest$owner; const manifestString = JSON.stringify(manifest); if (_cachedSignedManifest.manifestString === manifestString) { return _cachedSignedManifest.signedManifest; } // WARNING: Removing the following line will regress analytics, see: https://github.com/expo/expo-cli/pull/2357 // TODO: make this more obvious from code const user = await _internal().UserManager.ensureLoggedInAsync(); const { response } = await _internal().ApiV2.clientForUser(user).postAsync('manifest/sign', { args: { remoteUsername: (_manifest$owner = manifest.owner) !== null && _manifest$owner !== void 0 ? _manifest$owner : await _internal().UserManager.getCurrentUsernameAsync(), remotePackageName: manifest.slug }, manifest: manifest }); _cachedSignedManifest.manifestString = manifestString; _cachedSignedManifest.signedManifest = response; return response; } function getUnsignedManifestString(manifest) { const unsignedManifest = { manifestString: JSON.stringify(manifest), signature: 'UNSIGNED' }; return JSON.stringify(unsignedManifest); } //# sourceMappingURL=ManifestHandler.js.map