plusui-native-core 0.1.104 → 0.1.106

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 (49) hide show
  1. package/Core/.claude/settings.local.json +7 -0
  2. package/Core/CMakeLists.txt +1 -1
  3. package/Core/Features/API/Connect_API.ts +20 -52
  4. package/Core/Features/API/app-api.ts +28 -28
  5. package/Core/Features/API/browser-api.ts +38 -38
  6. package/Core/Features/API/clipboard-api.ts +21 -21
  7. package/Core/Features/API/display-api.ts +33 -33
  8. package/Core/Features/API/keyboard-api.ts +33 -33
  9. package/Core/Features/API/menu-api.ts +39 -39
  10. package/Core/Features/API/router-api.ts +23 -23
  11. package/Core/Features/API/tray-api.ts +22 -22
  12. package/Core/Features/API/webgpu-api.ts +55 -55
  13. package/Core/Features/App/app.cpp +128 -102
  14. package/Core/Features/Browser/browser.cpp +227 -227
  15. package/Core/Features/Browser/browser.ts +161 -161
  16. package/Core/Features/Clipboard/clipboard.cpp +235 -235
  17. package/Core/Features/Display/display.cpp +212 -212
  18. package/Core/Features/FileDrop/filedrop.cpp +448 -324
  19. package/Core/Features/FileDrop/filedrop.css +421 -421
  20. package/Core/Features/FileDrop/filedrop.ts +5 -9
  21. package/Core/Features/Keyboard/keyboard_linux.cpp +4 -0
  22. package/Core/Features/Router/router.cpp +62 -62
  23. package/Core/Features/Router/router.ts +113 -113
  24. package/Core/Features/Tray/tray.cpp +328 -324
  25. package/Core/Features/WebGPU/webgpu.cpp +948 -948
  26. package/Core/Features/Window/webview.cpp +1026 -1014
  27. package/Core/Features/Window/webview.ts +516 -516
  28. package/Core/Features/Window/window.cpp +2265 -1988
  29. package/Core/include/plusui/api.hpp +237 -237
  30. package/Core/include/plusui/app.hpp +33 -33
  31. package/Core/include/plusui/browser.hpp +67 -67
  32. package/Core/include/plusui/clipboard.hpp +41 -41
  33. package/Core/include/plusui/connect.hpp +340 -340
  34. package/Core/include/plusui/connection.hpp +3 -3
  35. package/Core/include/plusui/display.hpp +90 -90
  36. package/Core/include/plusui/filedrop.hpp +92 -77
  37. package/Core/include/plusui/keyboard.hpp +112 -112
  38. package/Core/include/plusui/menu.hpp +153 -153
  39. package/Core/include/plusui/plusui +18 -18
  40. package/Core/include/plusui/router.hpp +42 -42
  41. package/Core/include/plusui/tray.hpp +94 -94
  42. package/Core/include/plusui/webgpu.hpp +434 -434
  43. package/Core/include/plusui/window.hpp +180 -177
  44. package/Core/scripts/generate-umbrella-header.mjs +77 -77
  45. package/Core/vendor/WebView2EnvironmentOptions.h +406 -406
  46. package/Core/vendor/webview.h +487 -487
  47. package/Core/vendor/webview2.h +52079 -52079
  48. package/README.md +19 -19
  49. 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