one
Version:
One is a new React Framework that makes Vite serve both native and web.
699 lines (697 loc) • 24 kB
JavaScript
import * as http from "http";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { createRegistry, findServersByBundleId, findServerById, getAllServers, getRoute, setRoute, clearRoute, touchServer, pruneDeadServers, checkServerAlive, registerServer } from "./registry.native.js";
import { createIPCServer, getSocketPath, cleanupSocket, readServerFiles } from "./ipc.native.js";
import { proxyHttpRequest, proxyWebSocket } from "./proxy.native.js";
import { getBootedSimulators, resolvePendingPicker } from "./picker.native.js";
import colors from "picocolors";
function _type_of(obj) {
"@swc/helpers - typeof";
return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
}
var debugLogPath = path.join(os.homedir(), ".one", "daemon-debug.log");
function debugLog(msg) {
fs.appendFileSync(debugLogPath, `${(/* @__PURE__ */new Date()).toISOString()} ${msg}
`);
}
var serverAppNames = /* @__PURE__ */new Map();
async function getAppNameForServer(root) {
var cached = serverAppNames.get(root);
if (cached) return cached;
try {
var appJsonPath = path.join(root, "app.json");
if (fs.existsSync(appJsonPath)) {
var _content_expo;
var content = JSON.parse(fs.readFileSync(appJsonPath, "utf-8"));
var name = ((_content_expo = content.expo) === null || _content_expo === void 0 ? void 0 : _content_expo.name) || content.name;
if (name) {
serverAppNames.set(root, name);
return name;
}
}
for (var _i = 0, _iter = ["ts", "js"]; _i < _iter.length; _i++) {
var ext = _iter[_i];
var configPath = path.join(root, `app.config.${ext}`);
if (fs.existsSync(configPath)) {
var content1 = fs.readFileSync(configPath, "utf-8");
var nameMatch = content1.match(/name:\s*['"]([^'"]+)['"]/);
if (nameMatch) {
var name1 = nameMatch[1];
serverAppNames.set(root, name1);
return name1;
}
}
}
var dirName = path.basename(root);
if (dirName) {
serverAppNames.set(root, dirName);
return dirName;
}
} catch (err) {
debugLog(`Failed to get app name for ${root}: ${err}`);
}
return null;
}
var clientMappings = /* @__PURE__ */new Map();
var MAPPING_TTL_MS = 36e5;
setInterval(function () {
var now = Date.now();
var cleaned = 0;
var _iteratorNormalCompletion = true,
_didIteratorError = false,
_iteratorError = void 0;
try {
for (var _iterator = clientMappings[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var [key, info] = _step.value;
if (now - info.lastUsed > MAPPING_TTL_MS) {
clientMappings.delete(key);
cleaned++;
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
if (cleaned > 0) {
debugLog(`Cleaned ${cleaned} stale mappings`);
}
}, 3e4);
function getRouteIdFromCookies(req) {
var cookieHeader = req.headers.cookie;
if (!cookieHeader) return null;
var match = cookieHeader.match(/one-route-id=([^;]+)/);
return match ? match[1] : null;
}
var pendingMappings = /* @__PURE__ */new Map();
function setPendingMapping(serverId, simulatorUdid) {
pendingMappings.set(serverId, simulatorUdid);
debugLog(`Pending mapping: next request to server ${serverId} will map to sim ${simulatorUdid}`);
}
function clearMappingsForSimulator(simulatorUdid) {
var count = 0;
var _iteratorNormalCompletion = true,
_didIteratorError = false,
_iteratorError = void 0;
try {
for (var _iterator = clientMappings[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var [key, info] = _step.value;
if (info.simulatorUdid === simulatorUdid) {
clientMappings.delete(key);
count++;
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
debugLog(`Cleared ${count} mappings for simulator ${simulatorUdid}`);
}
function clearAllMappings() {
var count = clientMappings.size;
clientMappings.clear();
debugLog(`Cleared all ${count} client mappings`);
}
function getSimulatorMappings() {
var result = /* @__PURE__ */new Map();
var _iteratorNormalCompletion = true,
_didIteratorError = false,
_iteratorError = void 0;
try {
for (var _iterator = clientMappings[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var [_key, info] = _step.value;
if (info.simulatorUdid) {
result.set(info.simulatorUdid, info.serverId);
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return result;
}
function setSimulatorMapping(simulatorUdid, serverId) {
var key = `tui:${simulatorUdid}`;
clientMappings.set(key, {
serverId,
simulatorUdid,
matchedBy: "tui",
lastUsed: Date.now()
});
debugLog(`TUI set mapping: sim=${simulatorUdid} -> server=${serverId}`);
}
async function matchUserAgentToServer(headers, servers) {
var userAgent = headers["user-agent"];
if (!userAgent || typeof userAgent !== "string") return null;
var uaAppName = userAgent.split("/")[0];
if (!uaAppName) return null;
debugLog(`Trying to match user-agent app "${uaAppName}" to servers`);
var _iteratorNormalCompletion = true,
_didIteratorError = false,
_iteratorError = void 0;
try {
for (var _iterator = servers[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var server = _step.value;
var appName = await getAppNameForServer(server.root);
if (!appName) continue;
var normalizedUa = uaAppName.toLowerCase().replace(/\s+/g, "");
var normalizedApp = appName.toLowerCase().replace(/\s+/g, "");
debugLog(` Comparing "${normalizedUa}" to server app "${normalizedApp}"`);
if (normalizedUa === normalizedApp || normalizedUa.includes(normalizedApp) || normalizedApp.includes(normalizedUa)) {
debugLog(` Matched! ${uaAppName} -> ${server.root}`);
return server;
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return null;
}
function isGenericExpoAgent(ua) {
return ua.startsWith("Expo/") || ua.startsWith("Exponent/");
}
function extractAppNameFromUA(ua) {
var firstPart = ua.split(" ")[0] || "";
var appName = firstPart.split("/")[0];
return appName || null;
}
var recentConnections = /* @__PURE__ */new Map();
var CONNECTION_MEMORY_MS = 5e3;
function getPrimaryIdentifier(headers) {
var userAgent = headers["user-agent"] || "";
if (!isGenericExpoAgent(userAgent)) {
var appName = extractAppNameFromUA(userAgent);
if (appName) {
return `app:${appName}`;
}
}
var easClientId = headers["eas-client-id"];
if (easClientId && typeof easClientId === "string") {
return `eas:${easClientId}`;
}
if (userAgent) {
return `ua:${userAgent}`;
}
return null;
}
function lookupClient(headers) {
var identifier = getPrimaryIdentifier(headers);
if (!identifier) {
return {
info: null,
identifier: null
};
}
var info = clientMappings.get(identifier);
if (info) {
info.lastUsed = Date.now();
}
return {
info: info || null,
identifier
};
}
function saveClientMapping(identifier, serverId, simulatorUdid, matchedBy) {
clientMappings.set(identifier, {
serverId,
simulatorUdid,
matchedBy,
lastUsed: Date.now()
});
debugLog(`Saved mapping: ${identifier} -> server=${serverId}, sim=${simulatorUdid || "unknown"}, via=${matchedBy}`);
}
var DEFAULT_PORT = 8081;
var routeModeOverride = null;
function setRouteMode(mode) {
routeModeOverride = mode;
}
var activeDaemonState = null;
async function inferSimulator(clientInfo) {
var _unmappedSims_, _simulators_;
if (clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.simulatorUdid) return clientInfo.simulatorUdid;
var simulators = await getBootedSimulators();
var existingMappings = getSimulatorMappings();
var unmappedSims = simulators.filter(function (s) {
return !existingMappings.has(s.udid);
});
return ((_unmappedSims_ = unmappedSims[0]) === null || _unmappedSims_ === void 0 ? void 0 : _unmappedSims_.udid) || ((_simulators_ = simulators[0]) === null || _simulators_ === void 0 ? void 0 : _simulators_.udid);
}
async function resolveServer(state, headers, servers, bundleId) {
var {
info: clientInfo,
identifier
} = lookupClient(headers);
debugLog(`resolveServer: identifier=${identifier}, clientInfo=${JSON.stringify(clientInfo)}`);
var learnMapping = async function (server4, matchedBy) {
if (identifier && !(clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.simulatorUdid)) {
var simUdid = await inferSimulator(clientInfo);
if (simUdid) {
saveClientMapping(identifier, server4.id, simUdid, matchedBy);
return true;
}
}
return false;
};
if (servers.length === 1) {
var server = servers[0];
var learned = await learnMapping(server, "auto");
debugLog(`Single server: ${server.root}`);
return {
server,
learned
};
}
if (pendingMappings.size > 0 && identifier) {
var _iteratorNormalCompletion = true,
_didIteratorError = false,
_iteratorError = void 0;
try {
var _loop = function () {
var [serverId, simUdid] = _step.value;
var server4 = findServerById(state, serverId);
if (server4 && servers.some(function (s) {
return s.id === serverId;
})) {
debugLog(`TUI pending mapping: ${server4.root}, sim=${simUdid}`);
saveClientMapping(identifier, serverId, simUdid, "tui");
pendingMappings.delete(serverId);
return {
v: {
server: server4,
learned: true
}
};
}
};
for (var _iterator = pendingMappings[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var _ret = _loop();
if (_type_of(_ret) === "object") return _ret.v;
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
}
if (clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.simulatorUdid) {
var simRoute = getRoute(state, `sim:${clientInfo.simulatorUdid}`);
if (simRoute) {
var server1 = findServerById(state, simRoute.serverId);
if (server1) {
debugLog(`TUI cable route: sim=${clientInfo.simulatorUdid} -> ${server1.root}`);
return {
server: server1,
learned: false
};
}
}
}
if (clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.serverId) {
var server2 = findServerById(state, clientInfo.serverId);
if (server2) {
debugLog(`Cached mapping: ${identifier} -> ${server2.root}`);
return {
server: server2,
learned: false
};
}
}
var userAgent = headers["user-agent"] || "";
if (!isGenericExpoAgent(userAgent)) {
var matchedServer = await matchUserAgentToServer(headers, servers);
if (matchedServer) {
debugLog(`UA match: ${extractAppNameFromUA(userAgent)} -> ${matchedServer.root}`);
await learnMapping(matchedServer, "user-agent");
return {
server: matchedServer,
learned: true
};
}
}
var routeKey = bundleId || "default";
var fallbackRoute = getRoute(state, bundleId || "") || getRoute(state, "default");
if (fallbackRoute) {
var server3 = findServerById(state, fallbackRoute.serverId);
if (server3) {
debugLog(`Fallback route: ${server3.root}`);
await learnMapping(server3, "auto");
return {
server: server3,
learned: true
};
}
}
var mostRecent = [...servers].sort(function (a, b) {
return b.registeredAt - a.registeredAt;
})[0];
debugLog(`Most recent fallback: ${mostRecent.root}`);
setRoute(state, routeKey, mostRecent.id);
await learnMapping(mostRecent, "auto");
return {
server: mostRecent,
learned: true
};
}
function proxyAndTouch(req, res, server) {
var pendingSimId = pendingMappings.get(server.id);
if (pendingSimId) {
var identifier = getPrimaryIdentifier(req.headers);
if (identifier) {
saveClientMapping(identifier, server.id, pendingSimId, "tui");
pendingMappings.delete(server.id);
}
}
if (activeDaemonState) {
touchServer(activeDaemonState, server.id);
}
proxyHttpRequest(req, res, server);
}
async function startDaemon() {
var options = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
var port = options.port || DEFAULT_PORT;
var host = options.host || "0.0.0.0";
var quiet = options.quiet || false;
var log = quiet ? function () {
for (var _len = arguments.length, _args = new Array(_len), _key = 0; _key < _len; _key++) {
_args[_key] = arguments[_key];
}
} : console.log;
var state = createRegistry();
activeDaemonState = state;
var persistedServers = readServerFiles();
var _iteratorNormalCompletion = true,
_didIteratorError = false,
_iteratorError = void 0;
try {
for (var _iterator = persistedServers[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var ps = _step.value;
var alive = await checkServerAlive({
port: ps.port
});
if (alive) {
registerServer(state, {
port: ps.port,
bundleId: ps.bundleId,
root: ps.root
});
log(colors.cyan(`[daemon] Recovered server: ${ps.bundleId} \u2192 :${ps.port}`));
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
var ipcServer = createIPCServer(state, function (id) {
var server = findServerById(state, id);
if (server) {
var shortRoot = server.root.replace(process.env.HOME || "", "~");
log(colors.green(`[daemon] Server registered: ${server.bundleId} \u2192 :${server.port} (${shortRoot})`));
}
}, function (id) {
log(colors.yellow(`[daemon] Server unregistered: ${id}`));
});
var httpServer = http.createServer(async function (req, res) {
var _req_url, _req_socket;
debugLog(`${req.method} ${req.url}`);
if ((_req_url = req.url) === null || _req_url === void 0 ? void 0 : _req_url.startsWith("/__daemon")) {
await handleDaemonEndpoint(req, res, state);
return;
}
var url = new URL(req.url || "/", `http://${req.headers.host}`);
var bundleId = url.searchParams.get("app");
var servers = bundleId ? findServersByBundleId(state, bundleId) : getAllServers(state);
if (servers.length === 0) {
res.writeHead(404);
res.end(bundleId ? `No server for app: ${bundleId}` : "No servers registered");
return;
}
var {
server
} = await resolveServer(state, req.headers, servers, bundleId);
res.setHeader("Set-Cookie", `one-route-id=${server.id}; Path=/; Max-Age=3600`);
var remotePort = (_req_socket = req.socket) === null || _req_socket === void 0 ? void 0 : _req_socket.remotePort;
if (remotePort) {
recentConnections.set(remotePort, {
serverId: server.id,
timestamp: Date.now()
});
debugLog(`HTTP: port ${remotePort} -> ${server.root}`);
}
proxyAndTouch(req, res, server);
});
httpServer.on("upgrade", async function (req, rawSocket, head) {
var socket = rawSocket;
var url = new URL(req.url || "/", `http://${req.headers.host}`);
var bundleId = url.searchParams.get("app");
var servers = bundleId ? findServersByBundleId(state, bundleId) : getAllServers(state);
if (servers.length === 0) {
socket.end("HTTP/1.1 404 Not Found\r\n\r\n");
return;
}
var server;
var routeIdFromCookie = getRouteIdFromCookies(req);
if (routeIdFromCookie) {
server = findServerById(state, routeIdFromCookie);
if (server && servers.some(function (s) {
return s.id === server.id;
})) {
debugLog(`WebSocket: cookie route -> ${server.root}`);
} else {
server = void 0;
}
}
if (!server) {
var _req_socket;
var remotePort = (_req_socket = req.socket) === null || _req_socket === void 0 ? void 0 : _req_socket.remotePort;
if (remotePort) {
var recent = recentConnections.get(remotePort);
if (recent && Date.now() - recent.timestamp < CONNECTION_MEMORY_MS) {
server = findServerById(state, recent.serverId);
if (server) {
debugLog(`WebSocket: port ${remotePort} matched to ${server.root}`);
}
}
}
}
if (!server) {
var result = await resolveServer(state, req.headers, servers, bundleId);
server = result.server;
debugLog(`WebSocket: fallback -> ${server.root}`);
}
touchServer(state, server.id);
proxyWebSocket(req, socket, head, server);
});
httpServer.listen(port, host, function () {
log(colors.cyan("\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
log(colors.cyan(" one daemon"));
log(colors.cyan("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
log(`
Listening on ${colors.green(`http://${host === "0.0.0.0" ? "localhost" : host}:${port}`)}`);
log(` IPC socket: ${colors.dim(getSocketPath())}`);
log("");
log(colors.dim(" Waiting for dev servers to register..."));
log(colors.dim(" Run 'one dev' in your project directories"));
log("");
});
var HEALTH_CHECK_INTERVAL = 5e3;
var healthCheckInterval = setInterval(async function () {
var prunedCount = await pruneDeadServers(state, function (server) {
log(colors.yellow(`[daemon] Pruned dead server: ${server.bundleId} (port ${server.port})`));
});
if (prunedCount > 0) {
log(colors.dim(`[daemon] Pruned ${prunedCount} dead server(s)`));
}
}, HEALTH_CHECK_INTERVAL);
var shutdown = function () {
log(colors.yellow("\n[daemon] Shutting down..."));
clearInterval(healthCheckInterval);
httpServer.close();
ipcServer.close();
cleanupSocket();
process.exit(0);
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
return {
httpServer,
ipcServer,
state,
shutdown,
healthCheckInterval
};
}
async function handleDaemonEndpoint(req, res, state) {
var url = new URL(req.url || "/", `http://${req.headers.host}`);
if (url.pathname === "/__daemon/status") {
var servers = getAllServers(state);
var simulators = await getBootedSimulators();
var simMappings = getSimulatorMappings();
var simulatorRoutes = {};
var _iteratorNormalCompletion = true,
_didIteratorError = false,
_iteratorError = void 0;
try {
for (var _iterator = simMappings[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var [udid, serverId] = _step.value;
simulatorRoutes[udid] = serverId;
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
res.writeHead(200, {
"Content-Type": "application/json"
});
res.end(JSON.stringify({
servers: servers.map(function (s) {
return {
id: s.id,
port: s.port,
bundleId: s.bundleId,
root: s.root
};
}),
simulators,
simulatorRoutes,
routeMode: routeModeOverride || "most-recent"
}, null, 2));
return;
}
if (url.pathname === "/__daemon/route" && req.method === "POST") {
var bundleId = url.searchParams.get("bundleId");
var serverId1 = url.searchParams.get("serverId");
if (!bundleId || !serverId1) {
res.writeHead(400);
res.end("Missing bundleId or serverId");
return;
}
var server = findServerById(state, serverId1);
if (!server) {
res.writeHead(404);
res.end("Server not found");
return;
}
setRoute(state, bundleId, serverId1);
resolvePendingPicker(bundleId, serverId1);
res.writeHead(200);
res.end("Route set");
return;
}
if (url.pathname === "/__daemon/simulator-route" && req.method === "POST") {
var simulatorUdid = url.searchParams.get("simulatorUdid");
var serverId2 = url.searchParams.get("serverId");
if (!simulatorUdid || !serverId2) {
res.writeHead(400);
res.end("Missing simulatorUdid or serverId");
return;
}
var server1 = findServerById(state, serverId2);
if (!server1) {
res.writeHead(404);
res.end("Server not found");
return;
}
setSimulatorMapping(simulatorUdid, serverId2);
setPendingMapping(serverId2, simulatorUdid);
setRoute(state, `sim:${simulatorUdid}`, serverId2);
res.writeHead(200, {
"Content-Type": "application/json"
});
res.end(JSON.stringify({
ok: true
}));
return;
}
if (url.pathname === "/__daemon/simulator-route" && req.method === "DELETE") {
var simulatorUdid1 = url.searchParams.get("simulatorUdid");
if (!simulatorUdid1) {
res.writeHead(400);
res.end("Missing simulatorUdid");
return;
}
clearMappingsForSimulator(simulatorUdid1);
clearRoute(state, `sim:${simulatorUdid1}`);
res.writeHead(200, {
"Content-Type": "application/json"
});
res.end(JSON.stringify({
ok: true
}));
return;
}
res.writeHead(404);
res.end("Not found");
}
export { clearAllMappings, clearMappingsForSimulator, getSimulatorMappings, setPendingMapping, setRouteMode, setSimulatorMapping, startDaemon };
//# sourceMappingURL=server.native.js.map