Loading editor/src/node_embedded.h +102 −30 Original line number Diff line number Diff line Loading @@ -49,17 +49,14 @@ function getMod(scriptPath) { return { mod, p }; } globalThis.__blogiEmbeddedRun = function(reqPath, resPath) { try { const req = JSON.parse(fs.readFileSync(reqPath, 'utf8')); function executeRequest(req) { 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; return { status: 'success', missing_hook: true, result: null }; } const result = fn(req); Loading @@ -81,11 +78,17 @@ globalThis.__blogiEmbeddedRun = function(reqPath, resPath) { 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; return { status: 'success', html, result: { html } }; } return { status: 'success', missing_hook: false, result }; } fs.writeFileSync(resPath, JSON.stringify({ status: 'success', missing_hook: false, result }), 'utf8'); globalThis.__blogiEmbeddedRun = function(reqPath, resPath) { try { const req = JSON.parse(fs.readFileSync(reqPath, 'utf8')); const response = executeRequest(req); fs.writeFileSync(resPath, JSON.stringify(response), 'utf8'); return true; } catch (err) { const msg = (err && err.message) ? err.message : 'Unknown embedded runner error'; Loading @@ -99,6 +102,17 @@ globalThis.__blogiEmbeddedRun = function(reqPath, resPath) { }; )JS"; globalThis.__blogiEmbeddedRunJson = function(reqJson) { try { const req = JSON.parse(reqJson); const response = executeRequest(req); return JSON.stringify(response); } catch (err) { const msg = (err && err.message) ? err.message : 'Unknown embedded runner error'; return JSON.stringify({ status: 'error', message: msg }); } }; class EmbeddedRuntime { public: int runRequest(const std::string &reqPath, const std::string &resPath) { Loading Loading @@ -137,6 +151,46 @@ public: return 0; } int runRequestJson(const std::string &requestJson, std::string &responseJson) { std::lock_guard<std::mutex> lock(mutex_); if (!ensureInitializedLocked()) { return 1; } auto *isolate = setup_->isolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); auto context = setup_->context(); v8::Context::Scope contextScope(context); v8::TryCatch tryCatch(isolate); node::AsyncResource asyncResource(isolate, context->Global(), "blogi-editor-embedded-json"); auto fn = runFunctionJson_.Get(isolate); v8::Local<v8::Value> argv[1] = { v8::String::NewFromUtf8(isolate, requestJson.c_str()).ToLocalChecked() }; auto maybeRet = asyncResource.MakeCallback(fn, 1, argv); v8::Local<v8::Value> ret; if (maybeRet.IsEmpty() || !maybeRet.ToLocal(&ret)) { if (tryCatch.HasCaught()) { v8::String::Utf8Value exceptionUtf8(isolate, tryCatch.Exception()); const char *msg = *exceptionUtf8 ? *exceptionUtf8 : "unknown JS exception"; std::cerr << "Node embedded runtime error: run json call failed: " << msg << std::endl; } else { std::cerr << "Node embedded runtime error: run json call failed" << std::endl; } return 1; } v8::String::Utf8Value utf8(isolate, ret); responseJson = *utf8 ? *utf8 : ""; return 0; } private: bool ensureInitializedLocked() { if (initialized_) { Loading Loading @@ -208,6 +262,18 @@ private: } runFunction_.Reset(isolate, fnVal.As<v8::Function>()); v8::Local<v8::Value> fnJsonVal; auto fnJsonName = v8::String::NewFromUtf8(isolate, "__blogiEmbeddedRunJson").ToLocalChecked(); if (!context->Global()->Get(context, fnJsonName).ToLocal(&fnJsonVal) || !fnJsonVal->IsFunction()) { std::cerr << "Node embedded bootstrap failed: __blogiEmbeddedRunJson not found" << std::endl; setup_.reset(); node::TearDownOncePerProcess(); init_.reset(); return false; } runFunctionJson_.Reset(isolate, fnJsonVal.As<v8::Function>()); } initialized_ = true; Loading @@ -219,6 +285,7 @@ private: std::shared_ptr<node::InitializationResult> init_; std::unique_ptr<node::CommonEnvironmentSetup> setup_; v8::Global<v8::Function> runFunction_; v8::Global<v8::Function> runFunctionJson_; }; inline EmbeddedRuntime &runtime() { Loading @@ -231,4 +298,9 @@ inline int runNodeRunner(const std::string &reqPath, return runtime().runRequest(reqPath, resPath); } inline int runNodeRunnerJson(const std::string &requestJson, std::string &responseJson) { return runtime().runRequestJson(requestJson, responseJson); } } // namespace blogi::webedit::embedded editor/src/webedit_api.cpp +3 −32 Original line number Diff line number Diff line Loading @@ -65,30 +65,7 @@ std::string getEnvOrDefault(const char *key, const char *fallback) { return blogi::webedit::embedded::getEnvOrDefault(key, fallback); } std::string makeTempPath(const std::string &name) { std::ostringstream path; path << "/tmp/blogi_nodecatalog_" << std::chrono::high_resolution_clock::now().time_since_epoch().count() << "_" << name; return path.str(); } void writeFile(const std::string &path, const std::string &data) { std::ofstream out(path, std::ios::trunc); if (!out.is_open()) return; out << data; } std::string readFile(const std::string &path) { std::ifstream in(path); if (!in.is_open()) return ""; std::ostringstream ss; ss << in.rdbuf(); return ss.str(); } bool queryNodeWidgetDescriptor(const std::string &scriptPath, NodeWidgetDescriptor &desc) { std::string reqPath = makeTempPath("request.json"); std::string resPath = makeTempPath("response.json"); bool ok = false; json_object *req = json_object_new_object(); json_object_object_add(req, "script_path", json_object_new_string(scriptPath.c_str())); Loading @@ -99,26 +76,22 @@ bool queryNodeWidgetDescriptor(const std::string &scriptPath, NodeWidgetDescript json_object_object_add(req, "properties", json_object_new_object()); const char *reqJson = json_object_to_json_string_ext(req, JSON_C_TO_STRING_NOSLASHESCAPE); writeFile(reqPath, reqJson ? reqJson : "{}"); std::string reqJsonStr = reqJson ? reqJson : "{}"; json_object_put(req); std::string raw; try { (void)blogi::webedit::embedded::runNodeRunner(reqPath, resPath); (void)blogi::webedit::embedded::runNodeRunnerJson(reqJsonStr, raw); } catch (const std::exception &e) { std::cerr << "[webedit] node metadata failed for " << scriptPath << ": " << e.what() << std::endl; std::remove(reqPath.c_str()); std::remove(resPath.c_str()); return false; } catch (...) { std::cerr << "[webedit] node metadata failed for " << scriptPath << ": unknown exception" << std::endl; std::remove(reqPath.c_str()); std::remove(resPath.c_str()); return false; } std::string raw = readFile(resPath); json_object *res = raw.empty() ? nullptr : json_tokener_parse(raw.c_str()); if (res && json_object_is_type(res, json_type_object)) { json_object *statusObj = nullptr; Loading Loading @@ -159,8 +132,6 @@ bool queryNodeWidgetDescriptor(const std::string &scriptPath, NodeWidgetDescript } if (res) json_object_put(res); std::remove(reqPath.c_str()); std::remove(resPath.c_str()); return ok; } Loading editor/widgets/nodewidget/nodewidget_adapter.h +6 −9 Original line number Diff line number Diff line Loading @@ -610,9 +610,6 @@ protected: return out; } std::string reqPath = makeTempPath("request.json"); std::string resPath = makeTempPath("response.json"); try { json_object *req = json_object_new_object(); json_object_object_add(req, "script_path", json_object_new_string(scriptPath.c_str())); Loading @@ -631,13 +628,16 @@ protected: json_object_object_add(req, "payload", json_object_get(payload)); const char *reqJson = json_object_to_json_string_ext(req, JSON_C_TO_STRING_NOSLASHESCAPE); writeFile(reqPath, reqJson ? reqJson : "{}"); std::string reqJsonStr = reqJson ? reqJson : "{}"; json_object_put(req); int rc = blogi::webedit::embedded::runNodeRunner(reqPath, resPath); std::string resRaw; int rc = blogi::webedit::embedded::runNodeRunnerJson(reqJsonStr, resRaw); (void)rc; std::string resRaw = readFile(resPath); if (resRaw.empty()) { throw std::runtime_error("Empty JSON from node runner"); } json_object *res = json_tokener_parse(resRaw.c_str()); if (!res) throw std::runtime_error("Invalid JSON from node runner"); Loading Loading @@ -680,9 +680,6 @@ protected: out.ok = false; out.message = "Unknown NodeWidget call error"; } std::remove(reqPath.c_str()); std::remove(resPath.c_str()); return out; } Loading Loading
editor/src/node_embedded.h +102 −30 Original line number Diff line number Diff line Loading @@ -49,17 +49,14 @@ function getMod(scriptPath) { return { mod, p }; } globalThis.__blogiEmbeddedRun = function(reqPath, resPath) { try { const req = JSON.parse(fs.readFileSync(reqPath, 'utf8')); function executeRequest(req) { 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; return { status: 'success', missing_hook: true, result: null }; } const result = fn(req); Loading @@ -81,11 +78,17 @@ globalThis.__blogiEmbeddedRun = function(reqPath, resPath) { 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; return { status: 'success', html, result: { html } }; } return { status: 'success', missing_hook: false, result }; } fs.writeFileSync(resPath, JSON.stringify({ status: 'success', missing_hook: false, result }), 'utf8'); globalThis.__blogiEmbeddedRun = function(reqPath, resPath) { try { const req = JSON.parse(fs.readFileSync(reqPath, 'utf8')); const response = executeRequest(req); fs.writeFileSync(resPath, JSON.stringify(response), 'utf8'); return true; } catch (err) { const msg = (err && err.message) ? err.message : 'Unknown embedded runner error'; Loading @@ -99,6 +102,17 @@ globalThis.__blogiEmbeddedRun = function(reqPath, resPath) { }; )JS"; globalThis.__blogiEmbeddedRunJson = function(reqJson) { try { const req = JSON.parse(reqJson); const response = executeRequest(req); return JSON.stringify(response); } catch (err) { const msg = (err && err.message) ? err.message : 'Unknown embedded runner error'; return JSON.stringify({ status: 'error', message: msg }); } }; class EmbeddedRuntime { public: int runRequest(const std::string &reqPath, const std::string &resPath) { Loading Loading @@ -137,6 +151,46 @@ public: return 0; } int runRequestJson(const std::string &requestJson, std::string &responseJson) { std::lock_guard<std::mutex> lock(mutex_); if (!ensureInitializedLocked()) { return 1; } auto *isolate = setup_->isolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); auto context = setup_->context(); v8::Context::Scope contextScope(context); v8::TryCatch tryCatch(isolate); node::AsyncResource asyncResource(isolate, context->Global(), "blogi-editor-embedded-json"); auto fn = runFunctionJson_.Get(isolate); v8::Local<v8::Value> argv[1] = { v8::String::NewFromUtf8(isolate, requestJson.c_str()).ToLocalChecked() }; auto maybeRet = asyncResource.MakeCallback(fn, 1, argv); v8::Local<v8::Value> ret; if (maybeRet.IsEmpty() || !maybeRet.ToLocal(&ret)) { if (tryCatch.HasCaught()) { v8::String::Utf8Value exceptionUtf8(isolate, tryCatch.Exception()); const char *msg = *exceptionUtf8 ? *exceptionUtf8 : "unknown JS exception"; std::cerr << "Node embedded runtime error: run json call failed: " << msg << std::endl; } else { std::cerr << "Node embedded runtime error: run json call failed" << std::endl; } return 1; } v8::String::Utf8Value utf8(isolate, ret); responseJson = *utf8 ? *utf8 : ""; return 0; } private: bool ensureInitializedLocked() { if (initialized_) { Loading Loading @@ -208,6 +262,18 @@ private: } runFunction_.Reset(isolate, fnVal.As<v8::Function>()); v8::Local<v8::Value> fnJsonVal; auto fnJsonName = v8::String::NewFromUtf8(isolate, "__blogiEmbeddedRunJson").ToLocalChecked(); if (!context->Global()->Get(context, fnJsonName).ToLocal(&fnJsonVal) || !fnJsonVal->IsFunction()) { std::cerr << "Node embedded bootstrap failed: __blogiEmbeddedRunJson not found" << std::endl; setup_.reset(); node::TearDownOncePerProcess(); init_.reset(); return false; } runFunctionJson_.Reset(isolate, fnJsonVal.As<v8::Function>()); } initialized_ = true; Loading @@ -219,6 +285,7 @@ private: std::shared_ptr<node::InitializationResult> init_; std::unique_ptr<node::CommonEnvironmentSetup> setup_; v8::Global<v8::Function> runFunction_; v8::Global<v8::Function> runFunctionJson_; }; inline EmbeddedRuntime &runtime() { Loading @@ -231,4 +298,9 @@ inline int runNodeRunner(const std::string &reqPath, return runtime().runRequest(reqPath, resPath); } inline int runNodeRunnerJson(const std::string &requestJson, std::string &responseJson) { return runtime().runRequestJson(requestJson, responseJson); } } // namespace blogi::webedit::embedded
editor/src/webedit_api.cpp +3 −32 Original line number Diff line number Diff line Loading @@ -65,30 +65,7 @@ std::string getEnvOrDefault(const char *key, const char *fallback) { return blogi::webedit::embedded::getEnvOrDefault(key, fallback); } std::string makeTempPath(const std::string &name) { std::ostringstream path; path << "/tmp/blogi_nodecatalog_" << std::chrono::high_resolution_clock::now().time_since_epoch().count() << "_" << name; return path.str(); } void writeFile(const std::string &path, const std::string &data) { std::ofstream out(path, std::ios::trunc); if (!out.is_open()) return; out << data; } std::string readFile(const std::string &path) { std::ifstream in(path); if (!in.is_open()) return ""; std::ostringstream ss; ss << in.rdbuf(); return ss.str(); } bool queryNodeWidgetDescriptor(const std::string &scriptPath, NodeWidgetDescriptor &desc) { std::string reqPath = makeTempPath("request.json"); std::string resPath = makeTempPath("response.json"); bool ok = false; json_object *req = json_object_new_object(); json_object_object_add(req, "script_path", json_object_new_string(scriptPath.c_str())); Loading @@ -99,26 +76,22 @@ bool queryNodeWidgetDescriptor(const std::string &scriptPath, NodeWidgetDescript json_object_object_add(req, "properties", json_object_new_object()); const char *reqJson = json_object_to_json_string_ext(req, JSON_C_TO_STRING_NOSLASHESCAPE); writeFile(reqPath, reqJson ? reqJson : "{}"); std::string reqJsonStr = reqJson ? reqJson : "{}"; json_object_put(req); std::string raw; try { (void)blogi::webedit::embedded::runNodeRunner(reqPath, resPath); (void)blogi::webedit::embedded::runNodeRunnerJson(reqJsonStr, raw); } catch (const std::exception &e) { std::cerr << "[webedit] node metadata failed for " << scriptPath << ": " << e.what() << std::endl; std::remove(reqPath.c_str()); std::remove(resPath.c_str()); return false; } catch (...) { std::cerr << "[webedit] node metadata failed for " << scriptPath << ": unknown exception" << std::endl; std::remove(reqPath.c_str()); std::remove(resPath.c_str()); return false; } std::string raw = readFile(resPath); json_object *res = raw.empty() ? nullptr : json_tokener_parse(raw.c_str()); if (res && json_object_is_type(res, json_type_object)) { json_object *statusObj = nullptr; Loading Loading @@ -159,8 +132,6 @@ bool queryNodeWidgetDescriptor(const std::string &scriptPath, NodeWidgetDescript } if (res) json_object_put(res); std::remove(reqPath.c_str()); std::remove(resPath.c_str()); return ok; } Loading
editor/widgets/nodewidget/nodewidget_adapter.h +6 −9 Original line number Diff line number Diff line Loading @@ -610,9 +610,6 @@ protected: return out; } std::string reqPath = makeTempPath("request.json"); std::string resPath = makeTempPath("response.json"); try { json_object *req = json_object_new_object(); json_object_object_add(req, "script_path", json_object_new_string(scriptPath.c_str())); Loading @@ -631,13 +628,16 @@ protected: json_object_object_add(req, "payload", json_object_get(payload)); const char *reqJson = json_object_to_json_string_ext(req, JSON_C_TO_STRING_NOSLASHESCAPE); writeFile(reqPath, reqJson ? reqJson : "{}"); std::string reqJsonStr = reqJson ? reqJson : "{}"; json_object_put(req); int rc = blogi::webedit::embedded::runNodeRunner(reqPath, resPath); std::string resRaw; int rc = blogi::webedit::embedded::runNodeRunnerJson(reqJsonStr, resRaw); (void)rc; std::string resRaw = readFile(resPath); if (resRaw.empty()) { throw std::runtime_error("Empty JSON from node runner"); } json_object *res = json_tokener_parse(resRaw.c_str()); if (!res) throw std::runtime_error("Invalid JSON from node runner"); Loading Loading @@ -680,9 +680,6 @@ protected: out.ok = false; out.message = "Unknown NodeWidget call error"; } std::remove(reqPath.c_str()); std::remove(resPath.c_str()); return out; } Loading