diff --git a/CMakeLists.txt b/CMakeLists.txt index df6acc3..de08db5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,8 +118,8 @@ set(JSRUNTIME_APP_FILES if ( ENABLE_JSRUNTIME_SERVER ) add_definitions("-DENABLE_JSRUNTIME_SERVER") - add_definitions("-DWS_SERVER_PORT=9112") - set (JSRUNTIME_APP_FILES ${JSRUNTIME_APP_FILES} + add_definitions("-DWS_SERVER_PORT=5000") + set (JSRUNTIME_COMMON_FILES ${JSRUNTIME_COMMON_FILES} ${JSRUNTIME_COMMON_SOURCE_DIRECTORY}/JSRuntimeServer.cpp ) endif ( ENABLE_JSRUNTIME_SERVER ) @@ -134,6 +134,15 @@ add_library(${JSRUNTIME_LIBRARY_NAME} SHARED ${JSRUNTIME_ENGINE_FILES} ) +#JSRUNTIMECLIENTCONTAINER CHANGES +option(BUILD_JSRUNTIME_CONTAINER "BUILD_JSRUNTIME_CONTAINER" ON) +set(JSRUNTIME_CONTAINER_FILES + ${JSRUNTIME_COMMON_SOURCE_DIRECTORY}/JSRuntimeClientContainer.cpp +) +set(JSRUNTIME_FILES + ${JSRUNTIME_COMMON_SOURCE_DIRECTORY}/JSRuntimeContainer.cpp +) + set(JSRUNTIME_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/include/${JSRUNTIME_ENGINE_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/include/linux ${CMAKE_CURRENT_SOURCE_DIR}/src/jsc/jsc_lib ${JSRUNTIME_ENGINE_INCLUDE_DIRECTORIES}) set(JSRUNTIME_LIBRARY_LINK_DIRECTORIES ${JSRUNTIME_ENGINE_LIBRARY_LINK_DIRECTORIES}) @@ -192,6 +201,21 @@ if (BUILD_JSRUNTIME_CLIENT) target_link_libraries(jsruntime_client ${JSRUNTIME_LIBRARY_LINK_DIRECTORIES} ${JSRUNTIME_LINK_ETHANLIB} -lpthread) endif (BUILD_JSRUNTIME_CLIENT) +set(JSRUNTIMECONTAINER_LIBRARY_NAME "JSRuntimeContainer") + +if (BUILD_JSRUNTIME_CONTAINER) + add_library(${JSRUNTIMECONTAINER_LIBRARY_NAME} SHARED ${JSRUNTIME_FILES}) + target_include_directories(${JSRUNTIMECONTAINER_LIBRARY_NAME} PRIVATE ${JSRUNTIME_INCLUDE_DIRECTORIES}) + target_link_libraries(${JSRUNTIMECONTAINER_LIBRARY_NAME} ${JSRUNTIME_LIBRARY_LINK_DIRECTORIES} -lpthread) + + add_executable(jsruntime_container ${JSRUNTIME_CONTAINER_FILES}) + add_dependencies(jsruntime_container ${JSRUNTIMECONTAINER_LIBRARY_NAME}) + target_include_directories(jsruntime_container PRIVATE ${JSRUNTIME_INCLUDE_DIRECTORIES}) + set_target_properties(jsruntime_container PROPERTIES OUTPUT_NAME "JSRuntimeContainer") + target_link_libraries(jsruntime_container ${JSRUNTIME_LIBRARY_LINK_DIRECTORIES} -l${JSRUNTIMECONTAINER_LIBRARY_NAME} -lpthread) + +endif (BUILD_JSRUNTIME_CONTAINER) + set(UWEBSOCKETS_TARGET "Linux") if (APPLE) set(UWEBSOCKETS_TARGET "Darwin") diff --git a/include/JSRuntimeContainer.h b/include/JSRuntimeContainer.h new file mode 100644 index 0000000..7a0d6cf --- /dev/null +++ b/include/JSRuntimeContainer.h @@ -0,0 +1,43 @@ +#ifndef JSRUNTIMECONTAINER_H +#define JSRUNTIMECONTAINER_H + +#include +#include +#include + +class JSRuntimeContainer +{ +public: + enum Namespace { + NetworkNamespace = 0x01, + MountNamespace = 0x02, + IpcNamespace = 0x04, + PidNamespace = 0x08, + UserNamespace = 0x10, + UtsNamespace = 0x20 + }; + + // Get container IP address + static std::string getContainerIpAddress(const std::string& containerId); + + // Check if container exists + static bool isContainer(const std::string& containerId); + + // Execute function in container namespace + static bool nsEnter(const std::string& containerId, Namespace type, const std::function& func); + + // WebSocket client functions + static bool connectAndSend(const std::string& ip, const std::string& message); + static std::string buildLaunchMessage(const std::string& url, const std::string& options); + static std::string parseAppConfig(const std::string& configPath); + +private: + // Internal implementation functions + static bool nsEnterImpl(const std::string& containerId, Namespace type, const std::function& func); + static pid_t findContainerPid(const std::string& containerId); + static bool nsEnterWithPid(pid_t pid, int nsType, const std::function& func); + static void nsThread(int newNsFd, int nsType, bool* success, const std::function& func); +}; + +#endif // JSRUNTIMECONTAINER_H + diff --git a/include/JSRuntimeServer.h b/include/JSRuntimeServer.h index d9f0616..b56690c 100644 --- a/include/JSRuntimeServer.h +++ b/include/JSRuntimeServer.h @@ -19,6 +19,7 @@ #pragma once #include +#include #ifdef USE_WEBSOCKET_MOCK #include "websocketpp.hpp" @@ -40,7 +41,7 @@ class JSRuntimeServer static JSRuntimeServer *getInstance(); ~JSRuntimeServer() = default; - void initialize(int serverport, std::shared_ptr renderer); + void initialize(int serverport, std::shared_ptr renderer, std::shared_ptr externalHandler = nullptr); bool start(); bool stop(); @@ -77,4 +78,5 @@ class JSRuntimeServer ConnectionSet mConnections; int mServerPort; std::shared_ptr mRenderer; + std::shared_ptr mExternalHandler; }; diff --git a/src/JSRuntimeClientContainer.cpp b/src/JSRuntimeClientContainer.cpp new file mode 100644 index 0000000..8382be2 --- /dev/null +++ b/src/JSRuntimeClientContainer.cpp @@ -0,0 +1,31 @@ +#include "JSRuntimeContainer.h" +#include +#include +#include +#include +#include "NativeJSLogger.h" +int main() +{ + std::string containerId = "com.sky.as.apps_TestApp"; + const std::string basePath = "/opt/twocontext"; // constant base path + const std::vector apps = {"app1", "app2"}; + + std::string ipAddress = JSRuntimeContainer::getContainerIpAddress(containerId); + if (ipAddress.empty()) { + NativeJSLogger::log(ERROR, "Failed to retrieve IP address for container"); + return 1; + } + + for (const auto &app : apps) { + std::string url = basePath + std::string("/") + app + std::string("/index.html"); + if (access(url.c_str(), F_OK) == 0) { + std::string pathAppConfig = basePath + std::string("/") + app + std::string("/app.config"); + std::string options = JSRuntimeContainer::parseAppConfig(pathAppConfig); + std::string message = JSRuntimeContainer::buildLaunchMessage(url, options); + JSRuntimeContainer::connectAndSend(ipAddress, message); + } + } + + return 0; +} + diff --git a/src/JSRuntimeContainer.cpp b/src/JSRuntimeContainer.cpp new file mode 100644 index 0000000..4952eda --- /dev/null +++ b/src/JSRuntimeContainer.cpp @@ -0,0 +1,302 @@ +// JSRuntimeContainer.cpp +#include "JSRuntimeContainer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rapidjson/document.h" +#ifndef USE_WEBSOCKET_MOCK +#include +#include +#endif + +using namespace rapidjson; + +#ifndef USE_WEBSOCKET_MOCK +typedef websocketpp::client SimpleClient; +#endif + +void JSRuntimeContainer::nsThread(int newNsFd, int nsType, bool* success, const std::function& func) +{ + if (setns(newNsFd, nsType) != 0) + { + std::cerr << "Failed to switch into new namespace: " << strerror(errno) << std::endl; + *success = false; + return; + } + + func(); + *success = true; +} + +// Enter namespace using PID +bool JSRuntimeContainer::nsEnterWithPid(pid_t pid, int nsType, const std::function& func) +{ + const char* nsName; + + switch (nsType) + { + case CLONE_NEWIPC: + nsName = "ipc"; + break; + case CLONE_NEWNET: + nsName = "net"; + break; + case CLONE_NEWNS: + nsName = "mnt"; + break; + case CLONE_NEWPID: + nsName = "pid"; + break; + case CLONE_NEWUSER: + case CLONE_NEWUTS: + std::cerr << "Unsupported namespace type: " << nsType << std::endl; + return false; + default: + std::cerr << "Invalid namespace type: " << nsType << std::endl; + return false; + } + + char nsPath[64]; + snprintf(nsPath, sizeof(nsPath), "/proc/%d/ns/%s", pid, nsName); + + int newNsFd = open(nsPath, O_RDONLY | O_CLOEXEC); + if (newNsFd < 0) + { + std::cerr << "Failed to open container namespace: " << nsPath << " - " << strerror(errno) << std::endl; + return false; + } + + bool success = false; + + std::thread thread([=, &success, &func]() { + nsThread(newNsFd, nsType, &success, func); + }); + thread.join(); + + close(newNsFd); + + return success; +} + +// Find a PID inside the container +pid_t JSRuntimeContainer::findContainerPid(const std::string& containerId) +{ + std::string cgroupPath = "/sys/fs/cgroup/memory/" + containerId + "/cgroup.procs"; + + std::ifstream file(cgroupPath); + if (!file.is_open()) + { + return -1; + } + + std::string line; + if (!std::getline(file, line) || line.empty()) + { + return -1; + } + + long pid = std::strtol(line.c_str(), nullptr, 10); + if (pid >= INT32_MAX || pid <= 0) + { + return -1; + } + + return static_cast(pid); +} + +bool JSRuntimeContainer::nsEnterImpl(const std::string& containerId, Namespace type, const std::function& func) +{ + pid_t containerPid = findContainerPid(containerId); + if (containerPid <= 0) + { + return false; + } + + switch (type) + { + case NetworkNamespace: + return nsEnterWithPid(containerPid, CLONE_NEWNET, func); + case MountNamespace: + return nsEnterWithPid(containerPid, CLONE_NEWNS, func); + case IpcNamespace: + return nsEnterWithPid(containerPid, CLONE_NEWIPC, func); + default: + std::cerr << "Unknown namespace type" << std::endl; + return false; + } +} + +bool JSRuntimeContainer::nsEnter(const std::string& containerId, Namespace type, const std::function& func) +{ + return nsEnterImpl(containerId, type, func); +} + +std::string JSRuntimeContainer::getContainerIpAddress(const std::string& containerId) +{ + std::string ipAddress = ""; + + auto getIpAddress = [&ipAddress]() { + int sock = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (sock < 0) + { + return; + } + + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, "eth0"); + + if (ioctl(sock, SIOCGIFADDR, &ifr) < 0) + { + close(sock); + return; + } + + close(sock); + + struct sockaddr_in* ifaceAddr = reinterpret_cast(&ifr.ifr_addr); + char* ip = inet_ntoa(ifaceAddr->sin_addr); + if (ip != nullptr) + { + ipAddress = std::string(ip); + } + }; + + if (nsEnterImpl(containerId, NetworkNamespace, getIpAddress) && !ipAddress.empty()) + { + return ipAddress; + } + + return ""; +} + +bool JSRuntimeContainer::isContainer(const std::string& containerId) +{ + std::string cgroupPath = "/sys/fs/cgroup/memory/" + containerId + "/cgroup.procs"; + return (access(cgroupPath.c_str(), F_OK) == 0); +} + +bool JSRuntimeContainer::connectAndSend(const std::string& ip, const std::string& message) +{ +#ifdef USE_WEBSOCKET_MOCK + // In mock builds, just return true + return true; +#else + websocketpp::lib::error_code ec; + SimpleClient c; + c.clear_access_channels(websocketpp::log::alevel::all); + c.clear_error_channels(websocketpp::log::elevel::all); + c.init_asio(); + + std::string uri = std::string("ws://") + ip + std::string(":") + std::to_string(WS_SERVER_PORT); + SimpleClient::connection_ptr con = c.get_connection(uri, ec); + if (ec) { + return false; + } + + websocketpp::connection_hdl hdl = con->get_handle(); + c.connect(con); + + std::thread t(&SimpleClient::run, &c); + + // Wait briefly for open + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + try { + c.send(hdl, message, websocketpp::frame::opcode::text); + } catch (...) {} + + c.close(hdl, websocketpp::close::status::going_away, "bye", ec); + t.join(); + return true; +#endif +} + +// Build launchApplication JSON payload for a given URL and options +std::string JSRuntimeContainer::buildLaunchMessage(const std::string &url, const std::string& options) +{ + std::ostringstream oss; + oss << "{\"method\": \"launchApplication\", \"params\":{\"url\":\"" << url << "\",\"moduleSettings\":\"" << options << "\"}}"; + std::string message = oss.str(); + return message; +} + +std::string JSRuntimeContainer::parseAppConfig(const std::string& configPath) +{ + + std::string optionsStr; + std::ifstream fd(configPath); + if (!fd.is_open()) { + return optionsStr; + } + + std::stringstream buffer; + buffer << fd.rdbuf(); + std::string configData = buffer.str(); + fd.close(); + + Document configDoc; + if (configDoc.Parse(configData.c_str()).HasParseError()) { + return optionsStr; + } + + if (!configDoc.IsObject()) { + return optionsStr; + } + + if (configDoc.HasMember("features") && configDoc["features"].IsArray()) + { + const Value& flagsarr = configDoc["features"]; + for (SizeType i = 0; i < flagsarr.Size(); i++) { + const Value& flagOption = flagsarr[i]; + if (!flagOption.IsObject()) + continue; + + if (flagOption.HasMember("name") && flagOption.HasMember("enable")) { + const Value& name = flagOption["name"]; + const Value& enable = flagOption["enable"]; + + if (enable == true) { + std::string featureName = name.GetString(); + std::cout << "[DEBUG] parseAppConfig - processing enabled feature: " << featureName << std::endl; + + if (name == "player") + optionsStr += "player,"; + else if (name == "xhr") + optionsStr += "xhr,"; + else if (name == "websocket") + optionsStr += "ws,"; + else if (name == "http") + optionsStr += "http,"; + else if (name == "websocketenhanced") + optionsStr += "wsenhanced,"; + else if (name == "fetch") + optionsStr += "fetch,"; + else if (name == "jsdom") + optionsStr += "jsdom,"; + else if (name == "window") + optionsStr += "window,"; + + } + } + } + } + + if (!optionsStr.empty() && optionsStr.back() == ',') + optionsStr.pop_back(); + + return optionsStr; +} + + diff --git a/src/JSRuntimeServer.cpp b/src/JSRuntimeServer.cpp index 340a070..342f386 100644 --- a/src/JSRuntimeServer.cpp +++ b/src/JSRuntimeServer.cpp @@ -114,12 +114,15 @@ JSRuntimeServer::JSRuntimeServer() : mServerPort(0) { } -void JSRuntimeServer::initialize(int serverport, std::shared_ptr renderer) +void JSRuntimeServer::initialize(int serverport, std::shared_ptr renderer, std::shared_ptr externalHandler) { - NativeJSLogger::log(INFO, "Enter: %s\n", __func__); + NativeJSLogger::log(INFO, "JSRuntimeServer::initialize - port=%d, externalHandler=%p\n", serverport, externalHandler.get()); mServerPort = serverport; mRenderer = renderer; + mExternalHandler = externalHandler; + + NativeJSLogger::log(INFO, "JSRuntimeServer initialized - mExternalHandler=%p\n", mExternalHandler.get()); } bool JSRuntimeServer::start() @@ -202,7 +205,40 @@ void JSRuntimeServer::onMessage(websocketpp::connection_hdl hdl, message_ptr msg { break; } - if (method == "launchApplication") + if (method == "runExternalApplication") + { + JsonWrap jParams(jRoot, "params"); + if (jParams.get() == nullptr) + { + result = "error: missing params"; + break; + } + uint32_t id = jParams.getUint32("id", error); + if (error) + { + result = "error: invalid or missing id"; + break; + } + + std::string url = jParams.getString("url", error); + if (error) + { + result = "error: invalid or missing url"; + break; + } + + if (!mExternalHandler) + { + result = "error: external handler not available"; + NativeJSLogger::log(ERROR, "External handler not initialized\n"); + break; + } + + mExternalHandler->runExternalApplication(url, id); + result = "ok"; + NativeJSLogger::log(INFO, "Queued external application: id=%d, url=%s\n", id, url.c_str()); + } + else if (method == "launchApplication") { JsonWrap jParams(jRoot, "params"); if (jParams.get() == nullptr) @@ -217,13 +253,26 @@ void JSRuntimeServer::onMessage(websocketpp::connection_hdl hdl, message_ptr msg std::string options = jParams.getString("moduleSettings", error); ModuleSettings moduleSettings; moduleSettings.fromString(options); - uint32_t id = mRenderer->createApplication(moduleSettings); - mRenderer->runApplication(id, url); + uint32_t id = mRenderer->createApplication(moduleSettings); + + NativeJSLogger::log(INFO, "launchApplication: checking URL=%s for HTML extension\n", url.c_str()); + if (url.find(".html") != std::string::npos || url.find(".htm") != std::string::npos) + { + NativeJSLogger::log(INFO, "Detected HTML file, mExternalHandler=%p\n", mExternalHandler.get()); + if (mExternalHandler) + { + mExternalHandler->runExternalApplication(url, id); + } + } + else + { + mRenderer->runApplication(id, url); + } std::ostringstream oss; - oss<< "ID : " << id; - result = oss.str(); + oss<< "ID : " << id; + result = oss.str(); } - if (method == "createApplication") + else if (method == "createApplication") { JsonWrap jParams(jRoot, "params"); if (jParams.get() == nullptr) @@ -242,7 +291,7 @@ void JSRuntimeServer::onMessage(websocketpp::connection_hdl hdl, message_ptr msg oss<< "ID : " << id; result = oss.str(); } - if (method == "runApplication") + else if (method == "runApplication") { JsonWrap jParams(jRoot, "params"); if (jParams.get() == nullptr) diff --git a/src/jsruntime.cpp b/src/jsruntime.cpp index 2198a53..fdbf989 100644 --- a/src/jsruntime.cpp +++ b/src/jsruntime.cpp @@ -122,7 +122,7 @@ int main(int argc, char* argv[]) if (runServer == true) { JSRuntimeServer *server = JSRuntimeServer::getInstance(); - server->initialize(WS_SERVER_PORT, renderer); + server->initialize(WS_SERVER_PORT, renderer, nullptr); server->start(); } #endif