Loading editor/src/main.cpp +0 −5 Original line number Diff line number Diff line Loading @@ -50,11 +50,6 @@ static void signalHandler(int sig) { } #endif int main(int argc, char *argv[]) { if (int bridgeRc = blogi::webedit::nodebridge::runNodeRunnerFromArgs(argc, argv); bridgeRc >= 0) { return bridgeRc; } cmdplus::CmdController cmdCtl(cmdplus::CmdController::getInstance()); cmdCtl.registerCmd("help", 'h', false, "", "Show this help"); Loading editor/src/node_bridge.h +185 −219 Original line number Diff line number Diff line #pragma once #include <iostream> #include <cstring> #include <memory> #include <string> #include <vector> #ifdef _WIN32 #include <windows.h> #else #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #endif #include <mutex> #ifndef BLOGI_HAVE_LIBNODE #error "BLOGI_HAVE_LIBNODE must be defined: this build supports only embedded libnode" Loading @@ -19,9 +12,7 @@ #include <node.h> namespace blogi::webedit::nodebridge { inline constexpr const char *kBridgeRunnerArg = "--node-bridge-run"; namespace blogi::webedit::embedded { inline std::string getEnvOrDefault(const char *key, const char *fallback) { const char *v = std::getenv(key); Loading @@ -31,234 +22,209 @@ inline std::string getEnvOrDefault(const char *key, const char *fallback) { return std::string(fallback); } inline std::string getSelfExecutablePath() { #ifdef _WIN32 char buf[MAX_PATH]; DWORD len = GetModuleFileNameA(nullptr, buf, static_cast<DWORD>(sizeof(buf))); if (len == 0 || len >= sizeof(buf)) { return std::string(); } return std::string(buf, len); #else char buf[4096]; ssize_t len = readlink("/proc/self/exe", buf, sizeof(buf) - 1); if (len <= 0) { return std::string(); inline constexpr const char *kEmbeddedBootstrapScript = R"JS( 'use strict'; const fs = require('fs'); const path = require('path'); const loaded = new Map(); function resolveHook(mod, hook) { if (hook === 'render') { if (typeof mod === 'function') return mod; if (typeof mod.render === 'function') return mod.render; return null; } buf[len] = '\0'; return std::string(buf); #endif if (typeof mod[hook] === 'function') return mod[hook]; if (mod.hooks && typeof mod.hooks[hook] === 'function') return mod.hooks[hook]; return null; } inline int runNodeRunnerInProcess(const std::string &reqPath, const std::string &resPath) { static const std::string bridgeScript = "'use strict';" "const fs=require('fs');" "const path=require('path');" "(async()=>{" "const argv=process.argv;" "const requestFile=argv[1];" "const responseFile=argv[2];" "if(!requestFile||!responseFile)throw new Error('Usage: node -e <bridge> <request.json> <response.json>');" "const req=JSON.parse(fs.readFileSync(requestFile,'utf8'));" "const hook=(typeof req.hook==='string'&&req.hook)?req.hook:'render';" "if(!req.script_path)throw new Error('request.script_path is required');" "const scriptPath=path.resolve(req.script_path);" "const mod=require(scriptPath);" "const resolveHook=(m,h)=>{" "if(h==='render'){if(typeof m==='function')return m;if(typeof m.render==='function')return m.render;return null;}" "if(typeof m[h]==='function')return m[h];" "if(m.hooks&&typeof m.hooks[h]==='function')return m.hooks[h];" "return null;" "};" "const fn=resolveHook(mod,hook);" "if(!fn){fs.writeFileSync(responseFile,JSON.stringify({status:'success',missing_hook:true,result:null}),'utf8');return;}" "const result=await Promise.resolve(fn(req));" "if(hook==='metadata'&&result&&typeof result==='object'){" "if(!result.type_id||typeof result.type_id!=='string'||!result.type_id.trim()){" "const scriptBase=path.basename(scriptPath,path.extname(scriptPath));" "const widgetName=(typeof result.name==='string'&&result.name.trim())?result.name.trim():scriptBase;" "if(typeof mod.getUUID==='function')result.type_id=String(mod.getUUID());" "if(!result.type_id||!result.type_id.trim())throw new Error('Widget '+widgetName+' has no hardcoded UUID (type_id/getUUID missing)');" "}" "}" "if(hook==='render'){" "let html='';" "if(typeof result==='string')html=result;" "else if(result&&typeof result==='object'&&typeof result.html==='string')html=result.html;" "else throw new Error('render() must return a string or { html: string }');" "fs.writeFileSync(responseFile,JSON.stringify({status:'success',html,result:{html}}),'utf8');" "return;" "}" "fs.writeFileSync(responseFile,JSON.stringify({status:'success',missing_hook:false,result}),'utf8');" "})().catch((err)=>{" "const responseFile=process.argv[2];" "const payload=JSON.stringify({status:'error',message:(err&&err.message)?err.message:'Unknown Node runner error'});" "if(responseFile){try{fs.writeFileSync(responseFile,payload,'utf8');}catch(_){process.stderr.write(payload+'\\n');}}" "else{process.stderr.write(payload+'\\n');}" "process.exitCode=1;" "});"; std::vector<std::string> args; args.emplace_back("blogi-editor-node"); args.emplace_back(reqPath); args.emplace_back(resPath); bool initialized = false; auto teardown = [&initialized]() { if (initialized) { node::TearDownOncePerProcess(); initialized = false; function getMod(scriptPath) { const p = path.resolve(scriptPath); if (loaded.has(p)) return { mod: loaded.get(p), p }; const mod = require(p); loaded.set(p, mod); return { mod, p }; } }; auto init = node::InitializeOncePerProcess(args); if (!init) { std::cerr << "Node bridge init failed: InitializeOncePerProcess returned null" << std::endl; return 1; globalThis.__blogiEmbeddedRun = async function(reqPath, resPath) { try { const req = JSON.parse(fs.readFileSync(reqPath, 'utf8')); const hook = (typeof req.hook === 'string' && req.hook) ? req.hook : 'render'; if (!req.script_path) throw new Error('request.script_path is required'); const r = getMod(req.script_path); const fn = resolveHook(r.mod, hook); if (!fn) { fs.writeFileSync(resPath, JSON.stringify({ status: 'success', missing_hook: true, result: null }), 'utf8'); return true; } initialized = true; int rc = init->exit_code(); if (!init->errors().empty()) { for (const auto &err : init->errors()) { std::cerr << "Node bridge init warning/error: " << err << std::endl; const result = await Promise.resolve(fn(req)); if (hook === 'metadata' && result && typeof result === 'object') { if (!result.type_id || typeof result.type_id !== 'string' || !result.type_id.trim()) { const scriptBase = path.basename(r.p, path.extname(r.p)); const widgetName = (typeof result.name === 'string' && result.name.trim()) ? result.name.trim() : scriptBase; if (typeof r.mod.getUUID === 'function') result.type_id = String(r.mod.getUUID()); if (!result.type_id || !result.type_id.trim()) { throw new Error('Widget ' + widgetName + ' has no hardcoded UUID (type_id/getUUID missing)'); } } if (init->early_return()) { std::cerr << "Node bridge init aborted early with exit code " << rc << std::endl; teardown(); return rc; } std::vector<std::string> envErrors; auto setup = node::CommonEnvironmentSetup::Create( init->platform(), &envErrors, init->args(), init->exec_args()); if (!envErrors.empty()) { for (const auto &err : envErrors) { std::cerr << "Node bridge environment error: " << err << std::endl; if (hook === 'render') { let html = ''; if (typeof result === 'string') html = result; else if (result && typeof result === 'object' && typeof result.html === 'string') html = result.html; else throw new Error('render() must return a string or { html: string }'); fs.writeFileSync(resPath, JSON.stringify({ status: 'success', html, result: { html } }), 'utf8'); return true; } fs.writeFileSync(resPath, JSON.stringify({ status: 'success', missing_hook: false, result }), 'utf8'); return true; } catch (err) { const msg = (err && err.message) ? err.message : 'Unknown embedded runner error'; try { fs.writeFileSync(resPath, JSON.stringify({ status: 'error', message: msg }), 'utf8'); } catch (_) { process.stderr.write(msg + '\n'); } return false; } if (!setup) { std::cerr << "Node bridge setup failed: CommonEnvironmentSetup::Create returned null" << std::endl; teardown(); }; )JS"; class EmbeddedRuntime { public: int runRequest(const std::string &reqPath, const std::string &resPath) { std::lock_guard<std::mutex> lock(mutex_); if (!ensureInitializedLocked()) { return 1; } v8::Locker locker(setup->isolate()); v8::Isolate::Scope isolateScope(setup->isolate()); v8::HandleScope handleScope(setup->isolate()); v8::Context::Scope contextScope(setup->context()); auto *isolate = setup_->isolate(); auto context = setup_->context(); bool runtimeOk = true; { v8::Locker locker(setup->isolate()); v8::Isolate::Scope isolateScope(setup->isolate()); v8::HandleScope handleScope(setup->isolate()); v8::Context::Scope contextScope(setup->context()); if (node::LoadEnvironment(setup->env(), bridgeScript).IsEmpty()) { std::cerr << "Node bridge runtime error: LoadEnvironment returned empty result" << std::endl; runtimeOk = false; } else { auto loopResult = node::SpinEventLoop(setup->env()); if (loopResult.IsNothing()) { std::cerr << "Node bridge runtime error: SpinEventLoop returned no exit code" << std::endl; runtimeOk = false; } else { rc = loopResult.FromJust(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); v8::Context::Scope contextScope(context); auto fn = runFunction_.Get(isolate); v8::Local<v8::Value> argv[2] = { v8::String::NewFromUtf8(isolate, reqPath.c_str()).ToLocalChecked(), v8::String::NewFromUtf8(isolate, resPath.c_str()).ToLocalChecked() }; if (fn->Call(context, context->Global(), 2, argv).IsEmpty()) { std::cerr << "Node embedded runtime error: run function call failed" << std::endl; return 1; } auto loopResult = node::SpinEventLoop(setup_->env()); if (loopResult.IsNothing()) { std::cerr << "Node embedded runtime error: SpinEventLoop returned no exit code" << std::endl; return 1; } return loopResult.FromJust(); } setup.reset(); teardown(); return runtimeOk ? rc : 1; private: bool ensureInitializedLocked() { if (initialized_) { return true; } inline bool isBridgeRunnerInvocation(int argc, char *argv[]) { return argc == 4 && argv != nullptr && argv[1] != nullptr && std::strcmp(argv[1], kBridgeRunnerArg) == 0; std::vector<std::string> args = {"blogi-editor-embedded"}; init_ = node::InitializeOncePerProcess(args); if (!init_) { std::cerr << "Node embedded init failed: InitializeOncePerProcess returned null" << std::endl; return false; } inline int runNodeRunnerFromArgs(int argc, char *argv[]) { if (!isBridgeRunnerInvocation(argc, argv)) { return -1; if (!init_->errors().empty()) { for (const auto &err : init_->errors()) { std::cerr << "Node embedded init warning/error: " << err << std::endl; } return runNodeRunnerInProcess(argv[2], argv[3]); } inline int runNodeRunner(const std::string &reqPath, const std::string &resPath) { const std::string selfPath = getSelfExecutablePath(); if (selfPath.empty()) { return -1; if (init_->early_return()) { std::cerr << "Node embedded init aborted early with exit code " << init_->exit_code() << std::endl; node::TearDownOncePerProcess(); init_.reset(); return false; } #ifdef _WIN32 std::string cmdLine = "\"" + selfPath + "\" " + std::string(kBridgeRunnerArg) + " \"" + reqPath + "\" \"" + resPath + "\""; std::vector<char> mutableCmd(cmdLine.begin(), cmdLine.end()); mutableCmd.push_back('\0'); STARTUPINFOA si; PROCESS_INFORMATION pi; std::memset(&si, 0, sizeof(si)); std::memset(&pi, 0, sizeof(pi)); si.cb = sizeof(si); BOOL ok = CreateProcessA( selfPath.c_str(), mutableCmd.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi); if (!ok) { return -1; std::vector<std::string> envErrors; setup_ = node::CommonEnvironmentSetup::Create( init_->platform(), &envErrors, init_->args(), init_->exec_args()); if (!envErrors.empty()) { for (const auto &err : envErrors) { std::cerr << "Node embedded environment error: " << err << std::endl; } } if (!setup_) { node::TearDownOncePerProcess(); init_.reset(); return false; } WaitForSingleObject(pi.hProcess, INFINITE); DWORD exitCode = 1; GetExitCodeProcess(pi.hProcess, &exitCode); CloseHandle(pi.hThread); CloseHandle(pi.hProcess); return static_cast<int>(exitCode); #else pid_t pid = fork(); if (pid < 0) { return -1; auto *isolate = setup_->isolate(); auto context = setup_->context(); { v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); v8::Context::Scope contextScope(context); if (node::LoadEnvironment(setup_->env(), kEmbeddedBootstrapScript).IsEmpty()) { std::cerr << "Node embedded bootstrap failed: LoadEnvironment returned empty" << std::endl; setup_.reset(); node::TearDownOncePerProcess(); init_.reset(); return false; } if (pid == 0) { execl(selfPath.c_str(), selfPath.c_str(), kBridgeRunnerArg, reqPath.c_str(), resPath.c_str(), static_cast<char *>(nullptr)); _exit(127); v8::Local<v8::Value> fnVal; auto fnName = v8::String::NewFromUtf8(isolate, "__blogiEmbeddedRun").ToLocalChecked(); if (!context->Global()->Get(context, fnName).ToLocal(&fnVal) || !fnVal->IsFunction()) { std::cerr << "Node embedded bootstrap failed: __blogiEmbeddedRun not found" << std::endl; setup_.reset(); node::TearDownOncePerProcess(); init_.reset(); return false; } int status = 0; if (waitpid(pid, &status, 0) < 0) { return -1; runFunction_.Reset(isolate, fnVal.As<v8::Function>()); } if (WIFEXITED(status)) { return WEXITSTATUS(status); initialized_ = true; return true; } if (WIFSIGNALED(status)) { return 128 + WTERMSIG(status); std::mutex mutex_; bool initialized_ = false; std::shared_ptr<node::InitializationResult> init_; std::unique_ptr<node::CommonEnvironmentSetup> setup_; v8::Global<v8::Function> runFunction_; }; inline EmbeddedRuntime &runtime() { static EmbeddedRuntime r; return r; } return -1; #endif inline int runNodeRunner(const std::string &reqPath, const std::string &resPath) { return runtime().runRequest(reqPath, resPath); } } // namespace blogi::webedit::nodebridge } // namespace blogi::webedit::embedded editor/src/webedit_api.cpp +2 −2 Original line number Diff line number Diff line Loading @@ -59,7 +59,7 @@ struct NodeWidgetDescriptor { }; std::string getEnvOrDefault(const char *key, const char *fallback) { return blogi::webedit::nodebridge::getEnvOrDefault(key, fallback); return blogi::webedit::embedded::getEnvOrDefault(key, fallback); } std::string makeTempPath(const std::string &name) { Loading Loading @@ -99,7 +99,7 @@ bool queryNodeWidgetDescriptor(const std::string &scriptPath, NodeWidgetDescript writeFile(reqPath, reqJson ? reqJson : "{}"); json_object_put(req); (void)blogi::webedit::nodebridge::runNodeRunner(reqPath, resPath); (void)blogi::webedit::embedded::runNodeRunner(reqPath, resPath); std::string raw = readFile(resPath); json_object *res = raw.empty() ? nullptr : json_tokener_parse(raw.c_str()); Loading editor/widgets/nodewidget/nodewidget_adapter.h +1 −1 Original line number Diff line number Diff line Loading @@ -634,7 +634,7 @@ protected: writeFile(reqPath, reqJson ? reqJson : "{}"); json_object_put(req); int rc = blogi::webedit::nodebridge::runNodeRunner(reqPath, resPath); int rc = blogi::webedit::embedded::runNodeRunner(reqPath, resPath); (void)rc; std::string resRaw = readFile(resPath); Loading Loading
editor/src/main.cpp +0 −5 Original line number Diff line number Diff line Loading @@ -50,11 +50,6 @@ static void signalHandler(int sig) { } #endif int main(int argc, char *argv[]) { if (int bridgeRc = blogi::webedit::nodebridge::runNodeRunnerFromArgs(argc, argv); bridgeRc >= 0) { return bridgeRc; } cmdplus::CmdController cmdCtl(cmdplus::CmdController::getInstance()); cmdCtl.registerCmd("help", 'h', false, "", "Show this help"); Loading
editor/src/node_bridge.h +185 −219 Original line number Diff line number Diff line #pragma once #include <iostream> #include <cstring> #include <memory> #include <string> #include <vector> #ifdef _WIN32 #include <windows.h> #else #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #endif #include <mutex> #ifndef BLOGI_HAVE_LIBNODE #error "BLOGI_HAVE_LIBNODE must be defined: this build supports only embedded libnode" Loading @@ -19,9 +12,7 @@ #include <node.h> namespace blogi::webedit::nodebridge { inline constexpr const char *kBridgeRunnerArg = "--node-bridge-run"; namespace blogi::webedit::embedded { inline std::string getEnvOrDefault(const char *key, const char *fallback) { const char *v = std::getenv(key); Loading @@ -31,234 +22,209 @@ inline std::string getEnvOrDefault(const char *key, const char *fallback) { return std::string(fallback); } inline std::string getSelfExecutablePath() { #ifdef _WIN32 char buf[MAX_PATH]; DWORD len = GetModuleFileNameA(nullptr, buf, static_cast<DWORD>(sizeof(buf))); if (len == 0 || len >= sizeof(buf)) { return std::string(); } return std::string(buf, len); #else char buf[4096]; ssize_t len = readlink("/proc/self/exe", buf, sizeof(buf) - 1); if (len <= 0) { return std::string(); inline constexpr const char *kEmbeddedBootstrapScript = R"JS( 'use strict'; const fs = require('fs'); const path = require('path'); const loaded = new Map(); function resolveHook(mod, hook) { if (hook === 'render') { if (typeof mod === 'function') return mod; if (typeof mod.render === 'function') return mod.render; return null; } buf[len] = '\0'; return std::string(buf); #endif if (typeof mod[hook] === 'function') return mod[hook]; if (mod.hooks && typeof mod.hooks[hook] === 'function') return mod.hooks[hook]; return null; } inline int runNodeRunnerInProcess(const std::string &reqPath, const std::string &resPath) { static const std::string bridgeScript = "'use strict';" "const fs=require('fs');" "const path=require('path');" "(async()=>{" "const argv=process.argv;" "const requestFile=argv[1];" "const responseFile=argv[2];" "if(!requestFile||!responseFile)throw new Error('Usage: node -e <bridge> <request.json> <response.json>');" "const req=JSON.parse(fs.readFileSync(requestFile,'utf8'));" "const hook=(typeof req.hook==='string'&&req.hook)?req.hook:'render';" "if(!req.script_path)throw new Error('request.script_path is required');" "const scriptPath=path.resolve(req.script_path);" "const mod=require(scriptPath);" "const resolveHook=(m,h)=>{" "if(h==='render'){if(typeof m==='function')return m;if(typeof m.render==='function')return m.render;return null;}" "if(typeof m[h]==='function')return m[h];" "if(m.hooks&&typeof m.hooks[h]==='function')return m.hooks[h];" "return null;" "};" "const fn=resolveHook(mod,hook);" "if(!fn){fs.writeFileSync(responseFile,JSON.stringify({status:'success',missing_hook:true,result:null}),'utf8');return;}" "const result=await Promise.resolve(fn(req));" "if(hook==='metadata'&&result&&typeof result==='object'){" "if(!result.type_id||typeof result.type_id!=='string'||!result.type_id.trim()){" "const scriptBase=path.basename(scriptPath,path.extname(scriptPath));" "const widgetName=(typeof result.name==='string'&&result.name.trim())?result.name.trim():scriptBase;" "if(typeof mod.getUUID==='function')result.type_id=String(mod.getUUID());" "if(!result.type_id||!result.type_id.trim())throw new Error('Widget '+widgetName+' has no hardcoded UUID (type_id/getUUID missing)');" "}" "}" "if(hook==='render'){" "let html='';" "if(typeof result==='string')html=result;" "else if(result&&typeof result==='object'&&typeof result.html==='string')html=result.html;" "else throw new Error('render() must return a string or { html: string }');" "fs.writeFileSync(responseFile,JSON.stringify({status:'success',html,result:{html}}),'utf8');" "return;" "}" "fs.writeFileSync(responseFile,JSON.stringify({status:'success',missing_hook:false,result}),'utf8');" "})().catch((err)=>{" "const responseFile=process.argv[2];" "const payload=JSON.stringify({status:'error',message:(err&&err.message)?err.message:'Unknown Node runner error'});" "if(responseFile){try{fs.writeFileSync(responseFile,payload,'utf8');}catch(_){process.stderr.write(payload+'\\n');}}" "else{process.stderr.write(payload+'\\n');}" "process.exitCode=1;" "});"; std::vector<std::string> args; args.emplace_back("blogi-editor-node"); args.emplace_back(reqPath); args.emplace_back(resPath); bool initialized = false; auto teardown = [&initialized]() { if (initialized) { node::TearDownOncePerProcess(); initialized = false; function getMod(scriptPath) { const p = path.resolve(scriptPath); if (loaded.has(p)) return { mod: loaded.get(p), p }; const mod = require(p); loaded.set(p, mod); return { mod, p }; } }; auto init = node::InitializeOncePerProcess(args); if (!init) { std::cerr << "Node bridge init failed: InitializeOncePerProcess returned null" << std::endl; return 1; globalThis.__blogiEmbeddedRun = async function(reqPath, resPath) { try { const req = JSON.parse(fs.readFileSync(reqPath, 'utf8')); const hook = (typeof req.hook === 'string' && req.hook) ? req.hook : 'render'; if (!req.script_path) throw new Error('request.script_path is required'); const r = getMod(req.script_path); const fn = resolveHook(r.mod, hook); if (!fn) { fs.writeFileSync(resPath, JSON.stringify({ status: 'success', missing_hook: true, result: null }), 'utf8'); return true; } initialized = true; int rc = init->exit_code(); if (!init->errors().empty()) { for (const auto &err : init->errors()) { std::cerr << "Node bridge init warning/error: " << err << std::endl; const result = await Promise.resolve(fn(req)); if (hook === 'metadata' && result && typeof result === 'object') { if (!result.type_id || typeof result.type_id !== 'string' || !result.type_id.trim()) { const scriptBase = path.basename(r.p, path.extname(r.p)); const widgetName = (typeof result.name === 'string' && result.name.trim()) ? result.name.trim() : scriptBase; if (typeof r.mod.getUUID === 'function') result.type_id = String(r.mod.getUUID()); if (!result.type_id || !result.type_id.trim()) { throw new Error('Widget ' + widgetName + ' has no hardcoded UUID (type_id/getUUID missing)'); } } if (init->early_return()) { std::cerr << "Node bridge init aborted early with exit code " << rc << std::endl; teardown(); return rc; } std::vector<std::string> envErrors; auto setup = node::CommonEnvironmentSetup::Create( init->platform(), &envErrors, init->args(), init->exec_args()); if (!envErrors.empty()) { for (const auto &err : envErrors) { std::cerr << "Node bridge environment error: " << err << std::endl; if (hook === 'render') { let html = ''; if (typeof result === 'string') html = result; else if (result && typeof result === 'object' && typeof result.html === 'string') html = result.html; else throw new Error('render() must return a string or { html: string }'); fs.writeFileSync(resPath, JSON.stringify({ status: 'success', html, result: { html } }), 'utf8'); return true; } fs.writeFileSync(resPath, JSON.stringify({ status: 'success', missing_hook: false, result }), 'utf8'); return true; } catch (err) { const msg = (err && err.message) ? err.message : 'Unknown embedded runner error'; try { fs.writeFileSync(resPath, JSON.stringify({ status: 'error', message: msg }), 'utf8'); } catch (_) { process.stderr.write(msg + '\n'); } return false; } if (!setup) { std::cerr << "Node bridge setup failed: CommonEnvironmentSetup::Create returned null" << std::endl; teardown(); }; )JS"; class EmbeddedRuntime { public: int runRequest(const std::string &reqPath, const std::string &resPath) { std::lock_guard<std::mutex> lock(mutex_); if (!ensureInitializedLocked()) { return 1; } v8::Locker locker(setup->isolate()); v8::Isolate::Scope isolateScope(setup->isolate()); v8::HandleScope handleScope(setup->isolate()); v8::Context::Scope contextScope(setup->context()); auto *isolate = setup_->isolate(); auto context = setup_->context(); bool runtimeOk = true; { v8::Locker locker(setup->isolate()); v8::Isolate::Scope isolateScope(setup->isolate()); v8::HandleScope handleScope(setup->isolate()); v8::Context::Scope contextScope(setup->context()); if (node::LoadEnvironment(setup->env(), bridgeScript).IsEmpty()) { std::cerr << "Node bridge runtime error: LoadEnvironment returned empty result" << std::endl; runtimeOk = false; } else { auto loopResult = node::SpinEventLoop(setup->env()); if (loopResult.IsNothing()) { std::cerr << "Node bridge runtime error: SpinEventLoop returned no exit code" << std::endl; runtimeOk = false; } else { rc = loopResult.FromJust(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); v8::Context::Scope contextScope(context); auto fn = runFunction_.Get(isolate); v8::Local<v8::Value> argv[2] = { v8::String::NewFromUtf8(isolate, reqPath.c_str()).ToLocalChecked(), v8::String::NewFromUtf8(isolate, resPath.c_str()).ToLocalChecked() }; if (fn->Call(context, context->Global(), 2, argv).IsEmpty()) { std::cerr << "Node embedded runtime error: run function call failed" << std::endl; return 1; } auto loopResult = node::SpinEventLoop(setup_->env()); if (loopResult.IsNothing()) { std::cerr << "Node embedded runtime error: SpinEventLoop returned no exit code" << std::endl; return 1; } return loopResult.FromJust(); } setup.reset(); teardown(); return runtimeOk ? rc : 1; private: bool ensureInitializedLocked() { if (initialized_) { return true; } inline bool isBridgeRunnerInvocation(int argc, char *argv[]) { return argc == 4 && argv != nullptr && argv[1] != nullptr && std::strcmp(argv[1], kBridgeRunnerArg) == 0; std::vector<std::string> args = {"blogi-editor-embedded"}; init_ = node::InitializeOncePerProcess(args); if (!init_) { std::cerr << "Node embedded init failed: InitializeOncePerProcess returned null" << std::endl; return false; } inline int runNodeRunnerFromArgs(int argc, char *argv[]) { if (!isBridgeRunnerInvocation(argc, argv)) { return -1; if (!init_->errors().empty()) { for (const auto &err : init_->errors()) { std::cerr << "Node embedded init warning/error: " << err << std::endl; } return runNodeRunnerInProcess(argv[2], argv[3]); } inline int runNodeRunner(const std::string &reqPath, const std::string &resPath) { const std::string selfPath = getSelfExecutablePath(); if (selfPath.empty()) { return -1; if (init_->early_return()) { std::cerr << "Node embedded init aborted early with exit code " << init_->exit_code() << std::endl; node::TearDownOncePerProcess(); init_.reset(); return false; } #ifdef _WIN32 std::string cmdLine = "\"" + selfPath + "\" " + std::string(kBridgeRunnerArg) + " \"" + reqPath + "\" \"" + resPath + "\""; std::vector<char> mutableCmd(cmdLine.begin(), cmdLine.end()); mutableCmd.push_back('\0'); STARTUPINFOA si; PROCESS_INFORMATION pi; std::memset(&si, 0, sizeof(si)); std::memset(&pi, 0, sizeof(pi)); si.cb = sizeof(si); BOOL ok = CreateProcessA( selfPath.c_str(), mutableCmd.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi); if (!ok) { return -1; std::vector<std::string> envErrors; setup_ = node::CommonEnvironmentSetup::Create( init_->platform(), &envErrors, init_->args(), init_->exec_args()); if (!envErrors.empty()) { for (const auto &err : envErrors) { std::cerr << "Node embedded environment error: " << err << std::endl; } } if (!setup_) { node::TearDownOncePerProcess(); init_.reset(); return false; } WaitForSingleObject(pi.hProcess, INFINITE); DWORD exitCode = 1; GetExitCodeProcess(pi.hProcess, &exitCode); CloseHandle(pi.hThread); CloseHandle(pi.hProcess); return static_cast<int>(exitCode); #else pid_t pid = fork(); if (pid < 0) { return -1; auto *isolate = setup_->isolate(); auto context = setup_->context(); { v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); v8::Context::Scope contextScope(context); if (node::LoadEnvironment(setup_->env(), kEmbeddedBootstrapScript).IsEmpty()) { std::cerr << "Node embedded bootstrap failed: LoadEnvironment returned empty" << std::endl; setup_.reset(); node::TearDownOncePerProcess(); init_.reset(); return false; } if (pid == 0) { execl(selfPath.c_str(), selfPath.c_str(), kBridgeRunnerArg, reqPath.c_str(), resPath.c_str(), static_cast<char *>(nullptr)); _exit(127); v8::Local<v8::Value> fnVal; auto fnName = v8::String::NewFromUtf8(isolate, "__blogiEmbeddedRun").ToLocalChecked(); if (!context->Global()->Get(context, fnName).ToLocal(&fnVal) || !fnVal->IsFunction()) { std::cerr << "Node embedded bootstrap failed: __blogiEmbeddedRun not found" << std::endl; setup_.reset(); node::TearDownOncePerProcess(); init_.reset(); return false; } int status = 0; if (waitpid(pid, &status, 0) < 0) { return -1; runFunction_.Reset(isolate, fnVal.As<v8::Function>()); } if (WIFEXITED(status)) { return WEXITSTATUS(status); initialized_ = true; return true; } if (WIFSIGNALED(status)) { return 128 + WTERMSIG(status); std::mutex mutex_; bool initialized_ = false; std::shared_ptr<node::InitializationResult> init_; std::unique_ptr<node::CommonEnvironmentSetup> setup_; v8::Global<v8::Function> runFunction_; }; inline EmbeddedRuntime &runtime() { static EmbeddedRuntime r; return r; } return -1; #endif inline int runNodeRunner(const std::string &reqPath, const std::string &resPath) { return runtime().runRequest(reqPath, resPath); } } // namespace blogi::webedit::nodebridge } // namespace blogi::webedit::embedded
editor/src/webedit_api.cpp +2 −2 Original line number Diff line number Diff line Loading @@ -59,7 +59,7 @@ struct NodeWidgetDescriptor { }; std::string getEnvOrDefault(const char *key, const char *fallback) { return blogi::webedit::nodebridge::getEnvOrDefault(key, fallback); return blogi::webedit::embedded::getEnvOrDefault(key, fallback); } std::string makeTempPath(const std::string &name) { Loading Loading @@ -99,7 +99,7 @@ bool queryNodeWidgetDescriptor(const std::string &scriptPath, NodeWidgetDescript writeFile(reqPath, reqJson ? reqJson : "{}"); json_object_put(req); (void)blogi::webedit::nodebridge::runNodeRunner(reqPath, resPath); (void)blogi::webedit::embedded::runNodeRunner(reqPath, resPath); std::string raw = readFile(resPath); json_object *res = raw.empty() ? nullptr : json_tokener_parse(raw.c_str()); Loading
editor/widgets/nodewidget/nodewidget_adapter.h +1 −1 Original line number Diff line number Diff line Loading @@ -634,7 +634,7 @@ protected: writeFile(reqPath, reqJson ? reqJson : "{}"); json_object_put(req); int rc = blogi::webedit::nodebridge::runNodeRunner(reqPath, resPath); int rc = blogi::webedit::embedded::runNodeRunner(reqPath, resPath); (void)rc; std::string resRaw = readFile(resPath); Loading