plusui-native-core 0.1.105 → 0.1.107

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/Core/.cache/clangd/index/api.hpp.016B34C8046EF490.idx +0 -0
  2. package/Core/.cache/clangd/index/app.cpp.1E6FAF043D496421.idx +0 -0
  3. package/Core/.cache/clangd/index/app.hpp.FA0E5D412C4E6148.idx +0 -0
  4. package/Core/.cache/clangd/index/browser.cpp.9461A2CAF129F1D9.idx +0 -0
  5. package/Core/.cache/clangd/index/browser.hpp.BE40AE80881B3107.idx +0 -0
  6. package/Core/.cache/clangd/index/clipboard.cpp.2399913537B2A7AD.idx +0 -0
  7. package/Core/.cache/clangd/index/clipboard.hpp.C1095DDACD7149E9.idx +0 -0
  8. package/Core/.cache/clangd/index/connect.cpp.518C66C7C28B30A9.idx +0 -0
  9. package/Core/.cache/clangd/index/connect.hpp.08E2F7CD13B78601.idx +0 -0
  10. package/Core/.cache/clangd/index/connection.hpp.849FAEF1523BF2C3.idx +0 -0
  11. package/Core/.cache/clangd/index/display.cpp.F6F6D932BF9F8D8E.idx +0 -0
  12. package/Core/.cache/clangd/index/display.hpp.0C1A9CAD11EE4404.idx +0 -0
  13. package/Core/.cache/clangd/index/filedrop.cpp.669B524B3C501C52.idx +0 -0
  14. package/Core/.cache/clangd/index/filedrop.hpp.48460099C3F35F2D.idx +0 -0
  15. package/Core/.cache/clangd/index/keyboard.cpp.DC6D34E4A4F798DD.idx +0 -0
  16. package/Core/.cache/clangd/index/keyboard.hpp.F016CB68D7DE5A46.idx +0 -0
  17. package/Core/.cache/clangd/index/keyboard_linux.cpp.B403FDCEA7A6CA53.idx +0 -0
  18. package/Core/.cache/clangd/index/menu.cpp.3059F08D8D2DF265.idx +0 -0
  19. package/Core/.cache/clangd/index/menu.hpp.8716DCCC573910D4.idx +0 -0
  20. package/Core/.cache/clangd/index/plusui.hpp.8CFCDFDC2E3F41DD.idx +0 -0
  21. package/Core/.cache/clangd/index/router.cpp.EAC9EAD34C59E573.idx +0 -0
  22. package/Core/.cache/clangd/index/router.hpp.2B06E2EE9998468D.idx +0 -0
  23. package/Core/.cache/clangd/index/stb_image.h.E26CF48FE089A0D3.idx +0 -0
  24. package/Core/.cache/clangd/index/tray.cpp.92F244E7E1D7F0EC.idx +0 -0
  25. package/Core/.cache/clangd/index/tray.hpp.0D881B0601BBBD25.idx +0 -0
  26. package/Core/.cache/clangd/index/webgpu.cpp.FC656FA8BE10FE15.idx +0 -0
  27. package/Core/.cache/clangd/index/webgpu.hpp.5AF1A5E9DF9E5AE0.idx +0 -0
  28. package/Core/.cache/clangd/index/window.cpp.191D8C9ADF874B22.idx +0 -0
  29. package/Core/.cache/clangd/index/window.hpp.B9811B43AA295697.idx +0 -0
  30. package/Core/.claude/settings.local.json +7 -0
  31. package/Core/CMakeLists.txt +1 -1
  32. package/Core/Features/API/app-api.ts +28 -28
  33. package/Core/Features/API/browser-api.ts +38 -38
  34. package/Core/Features/API/clipboard-api.ts +21 -21
  35. package/Core/Features/API/display-api.ts +33 -33
  36. package/Core/Features/API/keyboard-api.ts +33 -33
  37. package/Core/Features/API/menu-api.ts +39 -39
  38. package/Core/Features/API/router-api.ts +23 -23
  39. package/Core/Features/API/tray-api.ts +22 -22
  40. package/Core/Features/API/webgpu-api.ts +55 -55
  41. package/Core/Features/App/app.cpp +135 -102
  42. package/Core/Features/Browser/browser.cpp +227 -227
  43. package/Core/Features/Browser/browser.ts +161 -161
  44. package/Core/Features/Clipboard/clipboard.cpp +235 -235
  45. package/Core/Features/Display/display.cpp +212 -212
  46. package/Core/Features/FileDrop/filedrop.cpp +448 -324
  47. package/Core/Features/FileDrop/filedrop.css +421 -421
  48. package/Core/Features/FileDrop/filedrop.ts +0 -7
  49. package/Core/Features/Keyboard/keyboard_linux.cpp +4 -0
  50. package/Core/Features/Router/router.cpp +62 -62
  51. package/Core/Features/Router/router.ts +113 -113
  52. package/Core/Features/Tray/tray.cpp +437 -324
  53. package/Core/Features/WebGPU/webgpu.cpp +948 -948
  54. package/Core/Features/Window/webview.cpp +1009 -1009
  55. package/Core/Features/Window/webview.ts +516 -516
  56. package/Core/Features/Window/window.cpp +2240 -1986
  57. package/Core/include/plusui/api.hpp +237 -237
  58. package/Core/include/plusui/app.hpp +36 -34
  59. package/Core/include/plusui/browser.hpp +67 -67
  60. package/Core/include/plusui/clipboard.hpp +41 -41
  61. package/Core/include/plusui/connect.hpp +340 -340
  62. package/Core/include/plusui/connection.hpp +3 -3
  63. package/Core/include/plusui/display.hpp +90 -90
  64. package/Core/include/plusui/filedrop.hpp +92 -77
  65. package/Core/include/plusui/keyboard.hpp +112 -112
  66. package/Core/include/plusui/menu.hpp +153 -153
  67. package/Core/include/plusui/plusui +18 -18
  68. package/Core/include/plusui/router.hpp +42 -42
  69. package/Core/include/plusui/tray.hpp +94 -94
  70. package/Core/include/plusui/webgpu.hpp +434 -434
  71. package/Core/include/plusui/window.hpp +180 -177
  72. package/Core/scripts/generate-umbrella-header.mjs +77 -77
  73. package/Core/vendor/WebView2EnvironmentOptions.h +406 -406
  74. package/Core/vendor/webview.h +487 -487
  75. package/Core/vendor/webview2.h +52079 -52079
  76. package/README.md +19 -19
  77. package/package.json +1 -1
@@ -1,11 +1,11 @@
1
- #pragma once
2
-
3
- #include <functional>
4
- #include <map>
5
- #include <nlohmann/json.hpp>
6
- #include <string>
7
- #include <vector>
8
-
1
+ #pragma once
2
+
3
+ #include <functional>
4
+ #include <map>
5
+ #include <nlohmann/json.hpp>
6
+ #include <string>
7
+ #include <vector>
8
+
9
9
  // ============================================================================
10
10
  // PlusUI Connect
11
11
  // ============================================================================
@@ -69,335 +69,335 @@
69
69
  // connect::settings.onSetTheme = [](json) { ... }; connect::settings.themeChange({{...}});
70
70
  //
71
71
  // ============================================================================
72
-
73
- namespace plusui {
74
-
75
- class Window;
76
-
77
- class Connect {
78
- public:
79
- using EventHandler = std::function<void(const nlohmann::json &)>;
80
- using CallHandler =
81
- std::function<nlohmann::json(const nlohmann::json &)>;
82
- using SubscriptionHandler =
83
- std::function<void(bool subscribed, const nlohmann::json &)>;
84
-
85
- class Feature {
86
- Connect *owner;
87
- std::string scope;
88
-
89
- std::string scopedName(const std::string &name) const {
90
- const std::string prefix = scope + ".";
91
- if (name.rfind(prefix, 0) == 0) {
92
- return name;
93
- }
94
- return prefix + name;
95
- }
96
-
97
- public:
98
- Feature(Connect *parent, std::string featureScope)
99
- : owner(parent), scope(std::move(featureScope)) {}
100
-
101
- void on(const std::string &name, EventHandler handler) {
102
- owner->on(scopedName(name), std::move(handler));
103
- }
104
-
105
- void emit(const std::string &name, const nlohmann::json &payload) {
106
- owner->emit(scopedName(name), payload);
107
- }
108
-
109
- void onCall(const std::string &name, CallHandler handler) {
110
- owner->onCall(scopedName(name), std::move(handler));
111
- }
112
-
113
- void onSubscription(const std::string &name,
114
- SubscriptionHandler handler) {
115
- owner->onSubscription(scopedName(name), std::move(handler));
116
- }
117
- };
118
-
119
- // ============================================================
120
- // Channel — single-name channel object (legacy / backward compat)
121
- //
122
- // Mirror of the TypeScript createChannel() object:
123
- // download.on([](const json& p) { ... });
124
- // download.emit({{"progress", 50}});
125
- //
126
- // Obtained via connect.channel("download") or declared in
127
- // the auto-generated connections.gen.hpp.
128
- // ============================================================
129
- class Channel {
130
- Connect *owner;
131
- std::string name_;
132
-
133
- public:
134
- Channel() : owner(nullptr) {}
135
- Channel(Connect *parent, std::string channelName)
136
- : owner(parent), name_(std::move(channelName)) {}
137
-
138
- // Register a listener — mirrors TypeScript: download.on((d) => { ... })
139
- void on(EventHandler handler) {
140
- if (owner) owner->on(name_, std::move(handler));
141
- }
142
-
143
- // Send a message — mirrors TypeScript: download.emit({ key: value })
144
- void emit(const nlohmann::json &payload = nlohmann::json::object()) {
145
- if (owner) owner->emit(name_, payload);
146
- }
147
-
148
- const std::string &name() const { return name_; }
149
- };
150
-
151
- private:
152
- std::function<void(const std::string &)> outboundHandler;
153
- std::map<std::string, std::vector<EventHandler>> eventHandlers;
154
- std::map<std::string, CallHandler> callHandlers;
155
- std::map<std::string, SubscriptionHandler> subscriptionHandlers;
156
-
157
- void emitEnvelope(const std::string &kind, const std::string &name,
158
- const nlohmann::json &payload,
159
- const std::string &id = std::string(),
160
- const std::string &error = std::string()) {
161
- if (!outboundHandler) {
162
- return;
163
- }
164
-
165
- nlohmann::json j;
166
- j["kind"] = kind;
167
- j["name"] = name;
168
- if (!id.empty()) {
169
- j["id"] = id;
170
- }
171
- j["payload"] = payload;
172
- if (!error.empty()) {
173
- j["error"] = error;
174
- }
175
-
176
- outboundHandler(j.dump());
177
- }
178
-
179
- public:
180
-
181
- virtual ~Connect() = default;
182
-
183
- // Mirror frontend API: register message listener by name
184
- void on(const std::string &name, EventHandler handler) {
185
- eventHandlers[name].push_back(std::move(handler));
186
- }
187
-
188
- Feature feature(const std::string &scope) { return Feature(this, scope); }
189
-
190
- // Create a named channel object — mirrors TypeScript createChannel('name')
191
- Channel channel(const std::string &name) { return Channel(this, name); }
192
-
193
- // Request/response helper for call primitive
194
- void onCall(const std::string &name, CallHandler handler) {
195
- callHandlers[name] = std::move(handler);
196
- }
197
-
198
- // Subscription helper for stream/query primitives
199
- void onSubscription(const std::string &name, SubscriptionHandler handler) {
200
- subscriptionHandlers[name] = std::move(handler);
201
- }
202
-
203
- // Setup the outbound message handler
204
- void setOutbound(std::function<void(const std::string &)> h) {
205
- outboundHandler = std::move(h);
206
- }
207
-
208
- void setOutboundHandler(std::function<void(const std::string &)> h) {
209
- setOutbound(std::move(h));
210
- }
211
-
212
- /**
213
- * emit() - Send a message to the frontend
214
- *
215
- * Works for all 5 communication patterns:
216
- * - SIMPLEX: emit("notification", {{"msg", "Hi!"}})
217
- * - CALL RESPONSE: emitResult(id, name, payload)
218
- * - STREAM/PUB-SUB: emit("updates", {{"value", x}}) // call repeatedly
219
- * - STATE: emit("theme", {{"mode", "dark"}})
220
- */
221
- void emit(const std::string &name, const nlohmann::json &payload) {
222
- emitEnvelope("event", name, payload);
223
- }
224
-
225
- void emitResult(const std::string &id, const std::string &name,
226
- const nlohmann::json &payload) {
227
- emitEnvelope("result", name, payload, id);
228
- }
229
-
230
- void emitError(const std::string &id, const std::string &name,
231
- const std::string &message) {
232
- emitEnvelope("error", name, nlohmann::json::object(), id, message);
233
- }
234
-
235
- /**
236
- * call() - Make a call to the frontend and wait for response
237
- *
238
- * Enables bidirectional request/response:
239
- * Backend: auto answer = connect.call("ui.promptUser", {{"msg", "Sure?"}});
240
- * Frontend: connect.ui.handlePromptUser = async (data) => { ... return result; };
241
- */
242
- void call(const std::string &name, const nlohmann::json &payload,
243
- std::function<void(const nlohmann::json &)> onResult = nullptr,
244
- std::function<void(const std::string &)> onError = nullptr) {
245
- auto id = std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
246
-
247
- if (onResult || onError) {
248
- // Store pending callback for when frontend responds
249
- on(name + ".__result__." + id, [this, id, name, onResult, onError](const nlohmann::json &p) {
250
- if (p.contains("error") && onError) {
251
- onError(p["error"].get<std::string>());
252
- } else if (onResult) {
253
- onResult(p.value("payload", nlohmann::json::object()));
254
- }
255
- });
256
- }
257
-
258
- emitEnvelope("call", name, payload, id);
259
- }
260
-
261
- // Parse and dispatch incoming messages from frontend
262
- void dispatchMessage(const std::string &message) {
263
- try {
264
- auto j = nlohmann::json::parse(message);
265
- nlohmann::json envelope = j;
266
-
267
- if (j.is_object() && j.value("method", std::string()) ==
268
- "connection.dispatch") {
269
- auto paramsIt = j.find("params");
270
- if (paramsIt != j.end() && paramsIt->is_array() &&
271
- !paramsIt->empty() && (*paramsIt)[0].is_object()) {
272
- envelope = (*paramsIt)[0];
273
- }
274
- }
275
-
276
- std::string name = envelope.value("name", "");
277
- nlohmann::json payload =
278
- envelope.value("payload", nlohmann::json::object());
279
- std::string kind = envelope.value("kind", "fire");
280
- std::string id = envelope.value("id", "");
281
-
282
- if (name.empty()) {
283
- return;
284
- }
285
-
286
- if (kind == "call") {
287
- try {
288
- nlohmann::json responsePayload = nlohmann::json::object();
289
- auto callIt = callHandlers.find(name);
290
- if (callIt != callHandlers.end()) {
291
- responsePayload = callIt->second(payload);
292
- } else {
293
- responsePayload = handleCall(name, payload);
294
- }
295
- if (!id.empty()) {
296
- emitResult(id, name, responsePayload);
297
- }
298
- } catch (const std::exception &e) {
299
- if (!id.empty()) {
300
- emitError(id, name, e.what());
301
- }
302
- } catch (...) {
303
- if (!id.empty()) {
304
- emitError(id, name, "Unknown error");
305
- }
306
- }
307
- return;
308
- }
309
-
310
- if (kind == "result" || kind == "error") {
311
- // Response from a frontend call — dispatch to pending listeners
312
- std::string resultKey = name + ".__result__." + id;
313
- auto listenersIt = eventHandlers.find(resultKey);
314
- if (listenersIt != eventHandlers.end()) {
315
- nlohmann::json resultPayload;
316
- if (kind == "error") {
317
- resultPayload["error"] = envelope.value("error", "Unknown error");
318
- } else {
319
- resultPayload["payload"] = payload;
320
- }
321
- for (const auto &listener : listenersIt->second) {
322
- listener(resultPayload);
323
- }
324
- eventHandlers.erase(resultKey);
325
- }
326
- return;
327
- }
328
-
329
- if (kind == "sub") {
330
- auto subIt = subscriptionHandlers.find(name);
331
- if (subIt != subscriptionHandlers.end()) {
332
- subIt->second(true, payload);
333
- }
334
- handleSubscription(name, true, payload);
335
- return;
336
- }
337
-
338
- if (kind == "unsub") {
339
- auto subIt = subscriptionHandlers.find(name);
340
- if (subIt != subscriptionHandlers.end()) {
341
- subIt->second(false, payload);
342
- }
343
- handleSubscription(name, false, payload);
344
- return;
345
- }
346
-
347
- auto listenersIt = eventHandlers.find(name);
348
- if (listenersIt != eventHandlers.end()) {
349
- for (const auto &listener : listenersIt->second) {
350
- listener(payload);
351
- }
352
- }
353
-
354
- handleMessage(name, payload);
355
- } catch (...) {
356
- // Silently ignore malformed messages
357
- }
358
- }
359
-
360
- void dispatch(const std::string &message) { dispatchMessage(message); }
361
-
362
- protected:
363
- virtual nlohmann::json handleCall(const std::string &name,
364
- const nlohmann::json &payload) {
365
- handleMessage(name, payload);
366
- return nlohmann::json::object();
367
- }
368
-
369
- virtual void handleSubscription(const std::string &name, bool subscribed,
370
- const nlohmann::json &payload) {
371
- (void)name;
372
- (void)subscribed;
373
- (void)payload;
374
- }
375
-
376
- /**
377
- * handleMessage() - Receive messages from the frontend
378
- *
379
- * Override this to handle incoming messages. This is your "on()" equivalent.
380
- *
381
- * For the new semantic API, prefer using the generated connect:: namespace
382
- * instead of overriding this method:
383
- *
384
- * connect::user.handleFetch = [](const json& p) -> json {
385
- * return database.getUser(p["id"]);
386
- * };
387
- *
388
- * This method remains for backward compatibility and catch-all handling.
389
- */
390
- virtual void handleMessage(const std::string &name,
391
- const nlohmann::json &payload) {
392
- (void)name;
393
- (void)payload;
394
- }
395
- };
396
-
397
- void bindConnect(Window &window, Connect &connect);
398
-
399
- // Legacy aliases for backwards compatibility
400
- using Bridge = Connect;
401
- using Connection = Connect;
402
-
403
- } // namespace plusui
72
+
73
+ namespace plusui {
74
+
75
+ class Window;
76
+
77
+ class Connect {
78
+ public:
79
+ using EventHandler = std::function<void(const nlohmann::json &)>;
80
+ using CallHandler =
81
+ std::function<nlohmann::json(const nlohmann::json &)>;
82
+ using SubscriptionHandler =
83
+ std::function<void(bool subscribed, const nlohmann::json &)>;
84
+
85
+ class Feature {
86
+ Connect *owner;
87
+ std::string scope;
88
+
89
+ std::string scopedName(const std::string &name) const {
90
+ const std::string prefix = scope + ".";
91
+ if (name.rfind(prefix, 0) == 0) {
92
+ return name;
93
+ }
94
+ return prefix + name;
95
+ }
96
+
97
+ public:
98
+ Feature(Connect *parent, std::string featureScope)
99
+ : owner(parent), scope(std::move(featureScope)) {}
100
+
101
+ void on(const std::string &name, EventHandler handler) {
102
+ owner->on(scopedName(name), std::move(handler));
103
+ }
104
+
105
+ void emit(const std::string &name, const nlohmann::json &payload) {
106
+ owner->emit(scopedName(name), payload);
107
+ }
108
+
109
+ void onCall(const std::string &name, CallHandler handler) {
110
+ owner->onCall(scopedName(name), std::move(handler));
111
+ }
112
+
113
+ void onSubscription(const std::string &name,
114
+ SubscriptionHandler handler) {
115
+ owner->onSubscription(scopedName(name), std::move(handler));
116
+ }
117
+ };
118
+
119
+ // ============================================================
120
+ // Channel — single-name channel object (legacy / backward compat)
121
+ //
122
+ // Mirror of the TypeScript createChannel() object:
123
+ // download.on([](const json& p) { ... });
124
+ // download.emit({{"progress", 50}});
125
+ //
126
+ // Obtained via connect.channel("download") or declared in
127
+ // the auto-generated connections.gen.hpp.
128
+ // ============================================================
129
+ class Channel {
130
+ Connect *owner;
131
+ std::string name_;
132
+
133
+ public:
134
+ Channel() : owner(nullptr) {}
135
+ Channel(Connect *parent, std::string channelName)
136
+ : owner(parent), name_(std::move(channelName)) {}
137
+
138
+ // Register a listener — mirrors TypeScript: download.on((d) => { ... })
139
+ void on(EventHandler handler) {
140
+ if (owner) owner->on(name_, std::move(handler));
141
+ }
142
+
143
+ // Send a message — mirrors TypeScript: download.emit({ key: value })
144
+ void emit(const nlohmann::json &payload = nlohmann::json::object()) {
145
+ if (owner) owner->emit(name_, payload);
146
+ }
147
+
148
+ const std::string &name() const { return name_; }
149
+ };
150
+
151
+ private:
152
+ std::function<void(const std::string &)> outboundHandler;
153
+ std::map<std::string, std::vector<EventHandler>> eventHandlers;
154
+ std::map<std::string, CallHandler> callHandlers;
155
+ std::map<std::string, SubscriptionHandler> subscriptionHandlers;
156
+
157
+ void emitEnvelope(const std::string &kind, const std::string &name,
158
+ const nlohmann::json &payload,
159
+ const std::string &id = std::string(),
160
+ const std::string &error = std::string()) {
161
+ if (!outboundHandler) {
162
+ return;
163
+ }
164
+
165
+ nlohmann::json j;
166
+ j["kind"] = kind;
167
+ j["name"] = name;
168
+ if (!id.empty()) {
169
+ j["id"] = id;
170
+ }
171
+ j["payload"] = payload;
172
+ if (!error.empty()) {
173
+ j["error"] = error;
174
+ }
175
+
176
+ outboundHandler(j.dump());
177
+ }
178
+
179
+ public:
180
+
181
+ virtual ~Connect() = default;
182
+
183
+ // Mirror frontend API: register message listener by name
184
+ void on(const std::string &name, EventHandler handler) {
185
+ eventHandlers[name].push_back(std::move(handler));
186
+ }
187
+
188
+ Feature feature(const std::string &scope) { return Feature(this, scope); }
189
+
190
+ // Create a named channel object — mirrors TypeScript createChannel('name')
191
+ Channel channel(const std::string &name) { return Channel(this, name); }
192
+
193
+ // Request/response helper for call primitive
194
+ void onCall(const std::string &name, CallHandler handler) {
195
+ callHandlers[name] = std::move(handler);
196
+ }
197
+
198
+ // Subscription helper for stream/query primitives
199
+ void onSubscription(const std::string &name, SubscriptionHandler handler) {
200
+ subscriptionHandlers[name] = std::move(handler);
201
+ }
202
+
203
+ // Setup the outbound message handler
204
+ void setOutbound(std::function<void(const std::string &)> h) {
205
+ outboundHandler = std::move(h);
206
+ }
207
+
208
+ void setOutboundHandler(std::function<void(const std::string &)> h) {
209
+ setOutbound(std::move(h));
210
+ }
211
+
212
+ /**
213
+ * emit() - Send a message to the frontend
214
+ *
215
+ * Works for all 5 communication patterns:
216
+ * - SIMPLEX: emit("notification", {{"msg", "Hi!"}})
217
+ * - CALL RESPONSE: emitResult(id, name, payload)
218
+ * - STREAM/PUB-SUB: emit("updates", {{"value", x}}) // call repeatedly
219
+ * - STATE: emit("theme", {{"mode", "dark"}})
220
+ */
221
+ void emit(const std::string &name, const nlohmann::json &payload) {
222
+ emitEnvelope("event", name, payload);
223
+ }
224
+
225
+ void emitResult(const std::string &id, const std::string &name,
226
+ const nlohmann::json &payload) {
227
+ emitEnvelope("result", name, payload, id);
228
+ }
229
+
230
+ void emitError(const std::string &id, const std::string &name,
231
+ const std::string &message) {
232
+ emitEnvelope("error", name, nlohmann::json::object(), id, message);
233
+ }
234
+
235
+ /**
236
+ * call() - Make a call to the frontend and wait for response
237
+ *
238
+ * Enables bidirectional request/response:
239
+ * Backend: auto answer = connect.call("ui.promptUser", {{"msg", "Sure?"}});
240
+ * Frontend: connect.ui.handlePromptUser = async (data) => { ... return result; };
241
+ */
242
+ void call(const std::string &name, const nlohmann::json &payload,
243
+ std::function<void(const nlohmann::json &)> onResult = nullptr,
244
+ std::function<void(const std::string &)> onError = nullptr) {
245
+ auto id = std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
246
+
247
+ if (onResult || onError) {
248
+ // Store pending callback for when frontend responds
249
+ on(name + ".__result__." + id, [this, id, name, onResult, onError](const nlohmann::json &p) {
250
+ if (p.contains("error") && onError) {
251
+ onError(p["error"].get<std::string>());
252
+ } else if (onResult) {
253
+ onResult(p.value("payload", nlohmann::json::object()));
254
+ }
255
+ });
256
+ }
257
+
258
+ emitEnvelope("call", name, payload, id);
259
+ }
260
+
261
+ // Parse and dispatch incoming messages from frontend
262
+ void dispatchMessage(const std::string &message) {
263
+ try {
264
+ auto j = nlohmann::json::parse(message);
265
+ nlohmann::json envelope = j;
266
+
267
+ if (j.is_object() && j.value("method", std::string()) ==
268
+ "connection.dispatch") {
269
+ auto paramsIt = j.find("params");
270
+ if (paramsIt != j.end() && paramsIt->is_array() &&
271
+ !paramsIt->empty() && (*paramsIt)[0].is_object()) {
272
+ envelope = (*paramsIt)[0];
273
+ }
274
+ }
275
+
276
+ std::string name = envelope.value("name", "");
277
+ nlohmann::json payload =
278
+ envelope.value("payload", nlohmann::json::object());
279
+ std::string kind = envelope.value("kind", "fire");
280
+ std::string id = envelope.value("id", "");
281
+
282
+ if (name.empty()) {
283
+ return;
284
+ }
285
+
286
+ if (kind == "call") {
287
+ try {
288
+ nlohmann::json responsePayload = nlohmann::json::object();
289
+ auto callIt = callHandlers.find(name);
290
+ if (callIt != callHandlers.end()) {
291
+ responsePayload = callIt->second(payload);
292
+ } else {
293
+ responsePayload = handleCall(name, payload);
294
+ }
295
+ if (!id.empty()) {
296
+ emitResult(id, name, responsePayload);
297
+ }
298
+ } catch (const std::exception &e) {
299
+ if (!id.empty()) {
300
+ emitError(id, name, e.what());
301
+ }
302
+ } catch (...) {
303
+ if (!id.empty()) {
304
+ emitError(id, name, "Unknown error");
305
+ }
306
+ }
307
+ return;
308
+ }
309
+
310
+ if (kind == "result" || kind == "error") {
311
+ // Response from a frontend call — dispatch to pending listeners
312
+ std::string resultKey = name + ".__result__." + id;
313
+ auto listenersIt = eventHandlers.find(resultKey);
314
+ if (listenersIt != eventHandlers.end()) {
315
+ nlohmann::json resultPayload;
316
+ if (kind == "error") {
317
+ resultPayload["error"] = envelope.value("error", "Unknown error");
318
+ } else {
319
+ resultPayload["payload"] = payload;
320
+ }
321
+ for (const auto &listener : listenersIt->second) {
322
+ listener(resultPayload);
323
+ }
324
+ eventHandlers.erase(resultKey);
325
+ }
326
+ return;
327
+ }
328
+
329
+ if (kind == "sub") {
330
+ auto subIt = subscriptionHandlers.find(name);
331
+ if (subIt != subscriptionHandlers.end()) {
332
+ subIt->second(true, payload);
333
+ }
334
+ handleSubscription(name, true, payload);
335
+ return;
336
+ }
337
+
338
+ if (kind == "unsub") {
339
+ auto subIt = subscriptionHandlers.find(name);
340
+ if (subIt != subscriptionHandlers.end()) {
341
+ subIt->second(false, payload);
342
+ }
343
+ handleSubscription(name, false, payload);
344
+ return;
345
+ }
346
+
347
+ auto listenersIt = eventHandlers.find(name);
348
+ if (listenersIt != eventHandlers.end()) {
349
+ for (const auto &listener : listenersIt->second) {
350
+ listener(payload);
351
+ }
352
+ }
353
+
354
+ handleMessage(name, payload);
355
+ } catch (...) {
356
+ // Silently ignore malformed messages
357
+ }
358
+ }
359
+
360
+ void dispatch(const std::string &message) { dispatchMessage(message); }
361
+
362
+ protected:
363
+ virtual nlohmann::json handleCall(const std::string &name,
364
+ const nlohmann::json &payload) {
365
+ handleMessage(name, payload);
366
+ return nlohmann::json::object();
367
+ }
368
+
369
+ virtual void handleSubscription(const std::string &name, bool subscribed,
370
+ const nlohmann::json &payload) {
371
+ (void)name;
372
+ (void)subscribed;
373
+ (void)payload;
374
+ }
375
+
376
+ /**
377
+ * handleMessage() - Receive messages from the frontend
378
+ *
379
+ * Override this to handle incoming messages. This is your "on()" equivalent.
380
+ *
381
+ * For the new semantic API, prefer using the generated connect:: namespace
382
+ * instead of overriding this method:
383
+ *
384
+ * connect::user.handleFetch = [](const json& p) -> json {
385
+ * return database.getUser(p["id"]);
386
+ * };
387
+ *
388
+ * This method remains for backward compatibility and catch-all handling.
389
+ */
390
+ virtual void handleMessage(const std::string &name,
391
+ const nlohmann::json &payload) {
392
+ (void)name;
393
+ (void)payload;
394
+ }
395
+ };
396
+
397
+ void bindConnect(Window &window, Connect &connect);
398
+
399
+ // Legacy aliases for backwards compatibility
400
+ using Bridge = Connect;
401
+ using Connection = Connect;
402
+
403
+ } // namespace plusui