319 lines
8.6 KiB
Plaintext
319 lines
8.6 KiB
Plaintext
/**
|
|
* @flow
|
|
*/
|
|
|
|
import joi from 'joi';
|
|
import os from 'os';
|
|
import path from 'path';
|
|
import url from 'url';
|
|
import promisify from 'util.promisify';
|
|
|
|
import ip from './ip';
|
|
import Config from './Config';
|
|
import ErrorCode from './ErrorCode';
|
|
import * as Exp from './Exp';
|
|
import * as ProjectSettings from './ProjectSettings';
|
|
import * as ProjectUtils from './project/ProjectUtils';
|
|
import * as Versions from './Versions';
|
|
import XDLError from './XDLError';
|
|
|
|
const joiValidateAsync = promisify(joi.validate);
|
|
|
|
export async function constructBundleUrlAsync(
|
|
projectRoot: string,
|
|
opts: any,
|
|
requestHostname?: string
|
|
) {
|
|
return constructUrlAsync(projectRoot, opts, true, requestHostname);
|
|
}
|
|
|
|
export async function constructManifestUrlAsync(
|
|
projectRoot: string,
|
|
opts: any,
|
|
requestHostname?: string
|
|
) {
|
|
return constructUrlAsync(projectRoot, opts, false, requestHostname);
|
|
}
|
|
|
|
export async function constructLogUrlAsync(projectRoot: string, requestHostname?: string) {
|
|
let baseUrl = await constructUrlAsync(projectRoot, { urlType: 'http' }, false, requestHostname);
|
|
return `${baseUrl}/logs`;
|
|
}
|
|
|
|
export async function constructUrlWithExtensionAsync(
|
|
projectRoot: string,
|
|
entryPoint: string,
|
|
ext: string,
|
|
requestHostname?: string
|
|
) {
|
|
let bundleUrl = await constructBundleUrlAsync(
|
|
projectRoot,
|
|
{
|
|
hostType: 'localhost',
|
|
urlType: 'http',
|
|
},
|
|
requestHostname
|
|
);
|
|
|
|
let mainModulePath = guessMainModulePath(entryPoint);
|
|
bundleUrl += `/${mainModulePath}.${ext}`;
|
|
|
|
let queryParams = await constructBundleQueryParamsAsync(
|
|
projectRoot,
|
|
{
|
|
dev: false,
|
|
minify: true,
|
|
},
|
|
requestHostname
|
|
);
|
|
return `${bundleUrl}?${queryParams}`;
|
|
}
|
|
|
|
export async function constructPublishUrlAsync(
|
|
projectRoot: string,
|
|
entryPoint: string,
|
|
requestHostname?: string
|
|
) {
|
|
return await constructUrlWithExtensionAsync(projectRoot, entryPoint, 'bundle', requestHostname);
|
|
}
|
|
|
|
export async function constructSourceMapUrlAsync(
|
|
projectRoot: string,
|
|
entryPoint: string,
|
|
requestHostname?: string
|
|
) {
|
|
return await constructUrlWithExtensionAsync(projectRoot, entryPoint, 'map', requestHostname);
|
|
}
|
|
|
|
export async function constructAssetsUrlAsync(
|
|
projectRoot: string,
|
|
entryPoint: string,
|
|
requestHostname?: string
|
|
) {
|
|
return await constructUrlWithExtensionAsync(projectRoot, entryPoint, 'assets', requestHostname);
|
|
}
|
|
|
|
export async function constructDebuggerHostAsync(projectRoot: string, requestHostname?: string) {
|
|
return constructUrlAsync(
|
|
projectRoot,
|
|
{
|
|
urlType: 'no-protocol',
|
|
},
|
|
true,
|
|
requestHostname
|
|
);
|
|
}
|
|
|
|
export async function constructBundleQueryParamsAsync(projectRoot: string, opts: any) {
|
|
let queryParams = `dev=${encodeURIComponent(!!opts.dev)}`;
|
|
|
|
if (opts.hasOwnProperty('strict')) {
|
|
queryParams += `&strict=${encodeURIComponent(!!opts.strict)}`;
|
|
}
|
|
|
|
if (opts.hasOwnProperty('minify')) {
|
|
queryParams += `&minify=${encodeURIComponent(!!opts.minify)}`;
|
|
}
|
|
|
|
queryParams += '&hot=false';
|
|
|
|
let { exp, pkg } = await ProjectUtils.readConfigJsonAsync(projectRoot);
|
|
|
|
// Be backwards compatible for users who haven't migrated from `exponent`
|
|
// to the `expo` sdk package.
|
|
let sdkPkg = pkg.dependencies['expo'] ? 'expo' : 'exponent';
|
|
// Use an absolute path here so that we can not worry about symlinks/relative requires
|
|
let nodeModulesPath = exp.nodeModulesPath
|
|
? path.join(path.resolve(projectRoot, exp.nodeModulesPath), 'node_modules')
|
|
: path.join(projectRoot, 'node_modules');
|
|
let pluginModule = path.join(nodeModulesPath, sdkPkg, 'tools', 'hashAssetFiles');
|
|
queryParams += `&assetPlugin=${pluginModule}`;
|
|
|
|
// Only sdk-10.1.0+ supports the assetPlugin parameter. We use only the
|
|
// major version in the sdkVersion field, so check for 11.0.0 to be sure.
|
|
let supportsAssetPlugins = Versions.gteSdkVersion(exp, '11.0.0');
|
|
if (!supportsAssetPlugins) {
|
|
queryParams += '&includeAssetFileHashes=true';
|
|
}
|
|
|
|
return queryParams;
|
|
}
|
|
|
|
export async function constructUrlAsync(
|
|
projectRoot: string,
|
|
opts: any,
|
|
isPackager: boolean,
|
|
requestHostname?: string
|
|
) {
|
|
if (opts) {
|
|
// the randomness is only important if we're online and can build a tunnel
|
|
let urlRandomnessSchema;
|
|
if (Config.offline) {
|
|
urlRandomnessSchema = joi
|
|
.string()
|
|
.optional()
|
|
.allow(null);
|
|
} else {
|
|
urlRandomnessSchema = joi.string();
|
|
}
|
|
|
|
let schema = joi.object().keys({
|
|
urlType: joi.any().valid('exp', 'http', 'redirect', 'no-protocol'),
|
|
lanType: joi.any().valid('ip', 'hostname'),
|
|
hostType: joi.any().valid('localhost', 'lan', 'tunnel'),
|
|
dev: joi.boolean(),
|
|
strict: joi.boolean(),
|
|
minify: joi.boolean(),
|
|
urlRandomness: urlRandomnessSchema,
|
|
});
|
|
|
|
try {
|
|
await joiValidateAsync(opts, schema);
|
|
} catch (e) {
|
|
throw new XDLError(ErrorCode.INVALID_OPTIONS, e.toString());
|
|
}
|
|
}
|
|
|
|
let defaultOpts = await ProjectSettings.getPackagerOptsAsync(projectRoot);
|
|
if (!opts) {
|
|
opts = defaultOpts;
|
|
} else {
|
|
opts = Object.assign({}, defaultOpts, opts);
|
|
}
|
|
|
|
let packagerInfo = await ProjectSettings.readPackagerInfoAsync(projectRoot);
|
|
|
|
let protocol;
|
|
if (opts.urlType === 'http') {
|
|
protocol = 'http';
|
|
} else if (opts.urlType === 'no-protocol') {
|
|
protocol = null;
|
|
} else {
|
|
protocol = 'exp';
|
|
|
|
let { exp } = await ProjectUtils.readConfigJsonAsync(projectRoot);
|
|
if (exp.detach && exp.detach.scheme) {
|
|
protocol = exp.detach.scheme;
|
|
}
|
|
}
|
|
|
|
let hostname;
|
|
let port;
|
|
|
|
const proxyURL = isPackager
|
|
? process.env.EXPO_PACKAGER_PROXY_URL
|
|
: process.env.EXPO_MANIFEST_PROXY_URL;
|
|
if (proxyURL) {
|
|
const parsedProxyURL = url.parse(proxyURL);
|
|
hostname = parsedProxyURL.hostname;
|
|
port = parsedProxyURL.port;
|
|
if (parsedProxyURL.protocol === 'https:') {
|
|
if (protocol === 'http') {
|
|
protocol = 'https';
|
|
}
|
|
if (!port) {
|
|
port = '443';
|
|
}
|
|
}
|
|
} else if (opts.hostType === 'localhost' || requestHostname === 'localhost') {
|
|
hostname = 'localhost';
|
|
port = isPackager ? packagerInfo.packagerPort : packagerInfo.expoServerPort;
|
|
} else if (opts.hostType === 'lan' || Config.offline) {
|
|
if (process.env.EXPO_PACKAGER_HOSTNAME) {
|
|
hostname = process.env.EXPO_PACKAGER_HOSTNAME;
|
|
} else if (process.env.REACT_NATIVE_PACKAGER_HOSTNAME) {
|
|
hostname = process.env.REACT_NATIVE_PACKAGER_HOSTNAME;
|
|
} else if (opts.lanType === 'ip') {
|
|
if (requestHostname) {
|
|
hostname = requestHostname;
|
|
} else {
|
|
hostname = ip.address();
|
|
}
|
|
} else {
|
|
// Some old versions of OSX work with hostname but not local ip address.
|
|
hostname = os.hostname();
|
|
}
|
|
port = isPackager ? packagerInfo.packagerPort : packagerInfo.expoServerPort;
|
|
} else {
|
|
let ngrokUrl = isPackager ? packagerInfo.packagerNgrokUrl : packagerInfo.expoServerNgrokUrl;
|
|
if (!ngrokUrl) {
|
|
// use localhost
|
|
hostname = 'localhost';
|
|
port = isPackager ? packagerInfo.packagerPort : packagerInfo.expoServerPort;
|
|
|
|
// TODO report a warning when this is for a currently served project, suppress for status checks
|
|
} else {
|
|
let pnu = url.parse(ngrokUrl);
|
|
hostname = pnu.hostname;
|
|
port = pnu.port;
|
|
}
|
|
}
|
|
|
|
let url_ = '';
|
|
if (protocol) {
|
|
url_ += `${protocol}://`;
|
|
}
|
|
|
|
if (!hostname) {
|
|
throw new Error('Hostname cannot be inferred.');
|
|
}
|
|
|
|
url_ += hostname;
|
|
|
|
if (port) {
|
|
url_ += `:${port}`;
|
|
} else {
|
|
// Android HMR breaks without this :|
|
|
url_ += ':80';
|
|
}
|
|
|
|
if (opts.urlType === 'redirect') {
|
|
return `https://exp.host/--/to-exp/${encodeURIComponent(url_)}`;
|
|
}
|
|
|
|
return url_;
|
|
}
|
|
|
|
export function guessMainModulePath(entryPoint: string) {
|
|
return entryPoint.replace(/\.js$/, '');
|
|
}
|
|
|
|
export function randomIdentifier(length: number = 6) {
|
|
let alphabet = '23456789qwertyuipasdfghjkzxcvbnm';
|
|
let result = '';
|
|
for (let i = 0; i < length; i++) {
|
|
let j = Math.floor(Math.random() * alphabet.length);
|
|
let c = alphabet.substr(j, 1);
|
|
result += c;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function sevenDigitIdentifier() {
|
|
return `${randomIdentifier(3)}-${randomIdentifier(4)}`;
|
|
}
|
|
|
|
export function randomIdentifierForUser(username: string) {
|
|
return `${username}-${randomIdentifier(3)}-${randomIdentifier(2)}`;
|
|
}
|
|
|
|
export function someRandomness() {
|
|
return [randomIdentifier(2), randomIdentifier(3)].join('-');
|
|
}
|
|
|
|
export function domainify(s: string) {
|
|
return s
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9-]/g, '-')
|
|
.replace(/^-+/, '')
|
|
.replace(/-+$/, '');
|
|
}
|
|
|
|
export function getPlatformSpecificBundleUrl(url: string, platform: string) {
|
|
if (url.includes(Exp.ENTRY_POINT_PLATFORM_TEMPLATE_STRING)) {
|
|
return url.replace(Exp.ENTRY_POINT_PLATFORM_TEMPLATE_STRING, platform);
|
|
} else {
|
|
return url;
|
|
}
|
|
}
|