UNPKG

one

Version:

One is a new React Framework that makes Vite serve both native and web.

699 lines (697 loc) 24 kB
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