@qontinui/ui-bridge 0.2.0 → 0.3.0
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.
- package/dist/ai/index.d.mts +4 -4
- package/dist/ai/index.d.ts +4 -4
- package/dist/babel-plugin/index.js +515 -0
- package/dist/babel-plugin/index.js.map +1 -0
- package/dist/babel-plugin/index.mjs +499 -0
- package/dist/babel-plugin/index.mjs.map +1 -0
- package/dist/control/index.d.mts +5 -5
- package/dist/control/index.d.ts +5 -5
- package/dist/core/index.d.mts +115 -44
- package/dist/core/index.d.ts +115 -44
- package/dist/core/index.js +0 -1560
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +1 -1549
- package/dist/core/index.mjs.map +1 -1
- package/dist/debug/index.d.mts +3 -3
- package/dist/debug/index.d.ts +3 -3
- package/dist/index.d.mts +7 -8
- package/dist/index.d.ts +7 -8
- package/dist/index.js +859 -873
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +860 -862
- package/dist/index.mjs.map +1 -1
- package/dist/{metrics-C9XRi_mL.d.ts → metrics-BfiT_rhZ.d.ts} +2 -2
- package/dist/{metrics-NC3csD0R.d.mts → metrics-DTA2bwG7.d.mts} +2 -2
- package/dist/native/control/index.js +453 -0
- package/dist/native/control/index.js.map +1 -0
- package/dist/native/control/index.mjs +450 -0
- package/dist/native/control/index.mjs.map +1 -0
- package/dist/native/core/index.js +486 -0
- package/dist/native/core/index.js.map +1 -0
- package/dist/native/core/index.mjs +475 -0
- package/dist/native/core/index.mjs.map +1 -0
- package/dist/native/debug/index.js +451 -0
- package/dist/native/debug/index.js.map +1 -0
- package/dist/native/debug/index.mjs +449 -0
- package/dist/native/debug/index.mjs.map +1 -0
- package/dist/native/index.js +2274 -0
- package/dist/native/index.js.map +1 -0
- package/dist/native/index.mjs +2246 -0
- package/dist/native/index.mjs.map +1 -0
- package/dist/native/react/index.js +1401 -0
- package/dist/native/react/index.js.map +1 -0
- package/dist/native/react/index.mjs +1389 -0
- package/dist/native/react/index.mjs.map +1 -0
- package/dist/native/server/index.js +415 -0
- package/dist/native/server/index.js.map +1 -0
- package/dist/native/server/index.mjs +410 -0
- package/dist/native/server/index.mjs.map +1 -0
- package/dist/react/index.d.mts +20 -7
- package/dist/react/index.d.ts +20 -7
- package/dist/react/index.js +42 -4
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +42 -4
- package/dist/react/index.mjs.map +1 -1
- package/dist/{registry-CIEDjbQ9.d.ts → registry-BKLEm-yk.d.ts} +9 -15
- package/dist/{registry-SsSDq46X.d.mts → registry-BmZgyCz8.d.mts} +9 -15
- package/dist/render-log/index.d.mts +1 -1
- package/dist/render-log/index.d.ts +1 -1
- package/dist/server/express.d.mts +36 -0
- package/dist/server/express.d.ts +36 -0
- package/dist/server/express.js +196 -0
- package/dist/server/express.js.map +1 -0
- package/dist/server/express.mjs +192 -0
- package/dist/server/express.mjs.map +1 -0
- package/dist/server/handlers.d.mts +93 -0
- package/dist/server/handlers.d.ts +93 -0
- package/dist/server/handlers.js +4278 -0
- package/dist/server/handlers.js.map +1 -0
- package/dist/server/handlers.mjs +4275 -0
- package/dist/server/handlers.mjs.map +1 -0
- package/dist/server/index.d.mts +10 -0
- package/dist/server/index.d.ts +10 -0
- package/dist/server/index.js +5352 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/index.mjs +5337 -0
- package/dist/server/index.mjs.map +1 -0
- package/dist/server/nextjs.d.mts +126 -0
- package/dist/server/nextjs.d.ts +126 -0
- package/dist/server/nextjs.js +287 -0
- package/dist/server/nextjs.js.map +1 -0
- package/dist/server/nextjs.mjs +282 -0
- package/dist/server/nextjs.mjs.map +1 -0
- package/dist/server/standalone.d.mts +6 -0
- package/dist/server/standalone.d.ts +6 -0
- package/dist/server/standalone.js +719 -0
- package/dist/server/standalone.js.map +1 -0
- package/dist/server/standalone.mjs +715 -0
- package/dist/server/standalone.mjs.map +1 -0
- package/dist/standalone-BURj8J3G.d.ts +212 -0
- package/dist/standalone-Dwmel29d.d.mts +212 -0
- package/dist/swc-plugin/index.d.mts +79 -0
- package/dist/swc-plugin/index.d.ts +79 -0
- package/dist/swc-plugin/index.js +15 -0
- package/dist/swc-plugin/index.js.map +1 -0
- package/dist/swc-plugin/index.mjs +9 -0
- package/dist/swc-plugin/index.mjs.map +1 -0
- package/dist/{types-CFT3Dnx4.d.mts → types-B5Q0GVo0.d.mts} +115 -3
- package/dist/{types-Dr6tH-bm.d.mts → types-B7J7noLK.d.mts} +1 -1
- package/dist/{types-oCTrRxSw.d.ts → types-BkNRILUa.d.ts} +1 -1
- package/dist/types-CEQLnFMv.d.mts +156 -0
- package/dist/types-CHnlwiTK.d.ts +156 -0
- package/dist/{types-BvCfFuEV.d.ts → types-DfPqwU-i.d.ts} +115 -3
- package/dist/{types-CPMbN_Iw.d.mts → types-jKVgTI6_.d.mts} +356 -160
- package/dist/{types-CPMbN_Iw.d.ts → types-jKVgTI6_.d.ts} +356 -160
- package/package.json +106 -3
- package/swc-plugin-wasm/ui_bridge_swc_plugin.wasm +0 -0
- package/dist/websocket-client-CX4QJesI.d.ts +0 -124
- package/dist/websocket-client-C_Na0OSp.d.mts +0 -124
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/server/types.ts
|
|
4
|
+
var UI_BRIDGE_ROUTES = [
|
|
5
|
+
// Render log
|
|
6
|
+
{ method: "GET", path: "/render-log", handler: "getRenderLog" },
|
|
7
|
+
{ method: "DELETE", path: "/render-log", handler: "clearRenderLog" },
|
|
8
|
+
{ method: "POST", path: "/render-log/snapshot", handler: "captureSnapshot" },
|
|
9
|
+
{ method: "GET", path: "/render-log/path", handler: "getRenderLogPath" },
|
|
10
|
+
// Control - Elements
|
|
11
|
+
{ method: "GET", path: "/control/elements", handler: "getElements" },
|
|
12
|
+
{ method: "GET", path: "/control/element/:id", handler: "getElement", params: ["id"] },
|
|
13
|
+
{ method: "GET", path: "/control/element/:id/state", handler: "getElementState", params: ["id"] },
|
|
14
|
+
{
|
|
15
|
+
method: "POST",
|
|
16
|
+
path: "/control/element/:id/action",
|
|
17
|
+
handler: "executeElementAction",
|
|
18
|
+
params: ["id"],
|
|
19
|
+
bodyRequired: true
|
|
20
|
+
},
|
|
21
|
+
// Control - Components
|
|
22
|
+
{ method: "GET", path: "/control/components", handler: "getComponents" },
|
|
23
|
+
{ method: "GET", path: "/control/component/:id", handler: "getComponent", params: ["id"] },
|
|
24
|
+
{ method: "GET", path: "/control/component/:id/state", handler: "getComponentState", params: ["id"] },
|
|
25
|
+
{
|
|
26
|
+
method: "POST",
|
|
27
|
+
path: "/control/component/:id/action/:actionId",
|
|
28
|
+
handler: "executeComponentAction",
|
|
29
|
+
params: ["id", "actionId"],
|
|
30
|
+
bodyRequired: true
|
|
31
|
+
},
|
|
32
|
+
// Find (formerly Discovery)
|
|
33
|
+
{ method: "POST", path: "/control/find", handler: "find" },
|
|
34
|
+
{ method: "POST", path: "/control/discover", handler: "discover" },
|
|
35
|
+
// @deprecated Use /control/find
|
|
36
|
+
{ method: "GET", path: "/control/snapshot", handler: "getControlSnapshot" },
|
|
37
|
+
// Workflows
|
|
38
|
+
{ method: "GET", path: "/control/workflows", handler: "getWorkflows" },
|
|
39
|
+
{ method: "POST", path: "/control/workflow/:id/run", handler: "runWorkflow", params: ["id"] },
|
|
40
|
+
{
|
|
41
|
+
method: "GET",
|
|
42
|
+
path: "/control/workflow/:runId/status",
|
|
43
|
+
handler: "getWorkflowStatus",
|
|
44
|
+
params: ["runId"]
|
|
45
|
+
},
|
|
46
|
+
// Debug
|
|
47
|
+
{ method: "GET", path: "/debug/action-history", handler: "getActionHistory" },
|
|
48
|
+
{ method: "GET", path: "/debug/metrics", handler: "getMetrics" },
|
|
49
|
+
{ method: "POST", path: "/debug/highlight/:id", handler: "highlightElement", params: ["id"] },
|
|
50
|
+
{ method: "GET", path: "/debug/element-tree", handler: "getElementTree" },
|
|
51
|
+
// AI-native endpoints
|
|
52
|
+
{ method: "POST", path: "/ai/search", handler: "aiSearch", bodyRequired: true },
|
|
53
|
+
{ method: "POST", path: "/ai/execute", handler: "aiExecute", bodyRequired: true },
|
|
54
|
+
{ method: "POST", path: "/ai/assert", handler: "aiAssert", bodyRequired: true },
|
|
55
|
+
{ method: "POST", path: "/ai/assert/batch", handler: "aiAssertBatch", bodyRequired: true },
|
|
56
|
+
{ method: "GET", path: "/ai/snapshot", handler: "getSemanticSnapshot" },
|
|
57
|
+
{ method: "GET", path: "/ai/diff", handler: "getSemanticDiff" },
|
|
58
|
+
{ method: "GET", path: "/ai/summary", handler: "getPageSummary" },
|
|
59
|
+
{ method: "POST", path: "/ai/semantic-search", handler: "aiSemanticSearch", bodyRequired: true }
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
// src/server/websocket-handler.ts
|
|
63
|
+
function generateId() {
|
|
64
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
65
|
+
}
|
|
66
|
+
var VERSION = "0.1.0";
|
|
67
|
+
var UIBridgeWSHandler = class {
|
|
68
|
+
constructor(handlers, options = {}) {
|
|
69
|
+
this.clients = /* @__PURE__ */ new Map();
|
|
70
|
+
this.handlers = handlers;
|
|
71
|
+
this.verbose = options.verbose ?? false;
|
|
72
|
+
this.log = options.log ?? console.log;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Handle new WebSocket connection
|
|
76
|
+
*/
|
|
77
|
+
handleConnection(ws) {
|
|
78
|
+
const clientId = generateId();
|
|
79
|
+
const client = {
|
|
80
|
+
id: clientId,
|
|
81
|
+
ws,
|
|
82
|
+
subscription: {
|
|
83
|
+
events: /* @__PURE__ */ new Set(),
|
|
84
|
+
elementIds: /* @__PURE__ */ new Set(),
|
|
85
|
+
componentIds: /* @__PURE__ */ new Set()
|
|
86
|
+
},
|
|
87
|
+
connectedAt: Date.now()
|
|
88
|
+
};
|
|
89
|
+
this.clients.set(clientId, client);
|
|
90
|
+
if (this.verbose) {
|
|
91
|
+
this.log(`[WS] Client connected: ${clientId}`);
|
|
92
|
+
}
|
|
93
|
+
ws.onmessage = (event) => {
|
|
94
|
+
this.handleMessage(clientId, event.data);
|
|
95
|
+
};
|
|
96
|
+
ws.onclose = () => {
|
|
97
|
+
this.handleDisconnect(clientId);
|
|
98
|
+
};
|
|
99
|
+
this.sendToClient(clientId, {
|
|
100
|
+
id: generateId(),
|
|
101
|
+
type: "welcome",
|
|
102
|
+
timestamp: Date.now(),
|
|
103
|
+
payload: {
|
|
104
|
+
version: VERSION,
|
|
105
|
+
features: {
|
|
106
|
+
renderLog: true,
|
|
107
|
+
control: true,
|
|
108
|
+
debug: true
|
|
109
|
+
},
|
|
110
|
+
clientId
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
return clientId;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Handle client disconnect
|
|
117
|
+
*/
|
|
118
|
+
handleDisconnect(clientId) {
|
|
119
|
+
this.clients.delete(clientId);
|
|
120
|
+
if (this.verbose) {
|
|
121
|
+
this.log(`[WS] Client disconnected: ${clientId}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Handle incoming message
|
|
126
|
+
*/
|
|
127
|
+
async handleMessage(clientId, data) {
|
|
128
|
+
const client = this.clients.get(clientId);
|
|
129
|
+
if (!client) return;
|
|
130
|
+
let message;
|
|
131
|
+
try {
|
|
132
|
+
message = JSON.parse(data);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
this.sendError(clientId, void 0, "PARSE_ERROR", "Invalid JSON message");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (this.verbose) {
|
|
138
|
+
this.log(`[WS] ${clientId} -> ${message.type}`);
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
switch (message.type) {
|
|
142
|
+
case "ping":
|
|
143
|
+
this.handlePing(clientId, message.id);
|
|
144
|
+
break;
|
|
145
|
+
case "subscribe":
|
|
146
|
+
await this.handleSubscribe(clientId, message);
|
|
147
|
+
break;
|
|
148
|
+
case "unsubscribe":
|
|
149
|
+
await this.handleUnsubscribe(clientId, message);
|
|
150
|
+
break;
|
|
151
|
+
case "find":
|
|
152
|
+
await this.handleFind(clientId, message);
|
|
153
|
+
break;
|
|
154
|
+
case "discover":
|
|
155
|
+
await this.handleFind(clientId, message);
|
|
156
|
+
break;
|
|
157
|
+
case "getElement":
|
|
158
|
+
await this.handleGetElement(clientId, message);
|
|
159
|
+
break;
|
|
160
|
+
case "getSnapshot":
|
|
161
|
+
await this.handleGetSnapshot(clientId, message);
|
|
162
|
+
break;
|
|
163
|
+
case "executeAction":
|
|
164
|
+
await this.handleExecuteAction(clientId, message);
|
|
165
|
+
break;
|
|
166
|
+
case "executeComponentAction":
|
|
167
|
+
await this.handleExecuteComponentAction(clientId, message);
|
|
168
|
+
break;
|
|
169
|
+
case "executeWorkflow":
|
|
170
|
+
await this.handleExecuteWorkflow(clientId, message);
|
|
171
|
+
break;
|
|
172
|
+
default:
|
|
173
|
+
this.sendError(
|
|
174
|
+
clientId,
|
|
175
|
+
message.id,
|
|
176
|
+
"UNKNOWN_MESSAGE",
|
|
177
|
+
`Unknown message type: ${message.type}`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
182
|
+
this.sendError(clientId, message.id, "HANDLER_ERROR", err.message);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Handle ping message
|
|
187
|
+
*/
|
|
188
|
+
handlePing(clientId, _requestId) {
|
|
189
|
+
this.sendToClient(clientId, {
|
|
190
|
+
id: generateId(),
|
|
191
|
+
type: "pong",
|
|
192
|
+
timestamp: Date.now()
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Handle subscribe message
|
|
197
|
+
*/
|
|
198
|
+
async handleSubscribe(clientId, message) {
|
|
199
|
+
const client = this.clients.get(clientId);
|
|
200
|
+
if (!client) return;
|
|
201
|
+
const { events, elementIds, componentIds } = message.payload;
|
|
202
|
+
if (events?.length) {
|
|
203
|
+
for (const event of events) {
|
|
204
|
+
client.subscription.events.add(event);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (elementIds?.length) {
|
|
208
|
+
for (const id of elementIds) {
|
|
209
|
+
client.subscription.elementIds.add(id);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (componentIds?.length) {
|
|
213
|
+
for (const id of componentIds) {
|
|
214
|
+
client.subscription.componentIds.add(id);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
this.sendToClient(clientId, {
|
|
218
|
+
id: generateId(),
|
|
219
|
+
type: "subscribed",
|
|
220
|
+
timestamp: Date.now(),
|
|
221
|
+
payload: {
|
|
222
|
+
events: Array.from(client.subscription.events)
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Handle unsubscribe message
|
|
228
|
+
*/
|
|
229
|
+
async handleUnsubscribe(clientId, message) {
|
|
230
|
+
const client = this.clients.get(clientId);
|
|
231
|
+
if (!client) return;
|
|
232
|
+
const { events } = message.payload;
|
|
233
|
+
let removedEvents;
|
|
234
|
+
if (events?.length) {
|
|
235
|
+
removedEvents = events.filter((e) => client.subscription.events.has(e));
|
|
236
|
+
for (const event of events) {
|
|
237
|
+
client.subscription.events.delete(event);
|
|
238
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
removedEvents = Array.from(client.subscription.events);
|
|
241
|
+
client.subscription.events.clear();
|
|
242
|
+
client.subscription.elementIds.clear();
|
|
243
|
+
client.subscription.componentIds.clear();
|
|
244
|
+
}
|
|
245
|
+
this.sendToClient(clientId, {
|
|
246
|
+
id: generateId(),
|
|
247
|
+
type: "unsubscribed",
|
|
248
|
+
timestamp: Date.now(),
|
|
249
|
+
payload: {
|
|
250
|
+
events: removedEvents
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Handle find message
|
|
256
|
+
*/
|
|
257
|
+
async handleFind(clientId, message) {
|
|
258
|
+
const result = await this.handlers.find(message.payload || {});
|
|
259
|
+
if (result.success && result.data) {
|
|
260
|
+
this.sendResponse(clientId, message.id, true, { elements: result.data.elements });
|
|
261
|
+
} else {
|
|
262
|
+
this.sendResponse(clientId, message.id, false, void 0, result.error);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Handle getElement message
|
|
267
|
+
*/
|
|
268
|
+
async handleGetElement(clientId, message) {
|
|
269
|
+
const { elementId } = message.payload;
|
|
270
|
+
const result = await this.handlers.getElement(elementId);
|
|
271
|
+
if (result.success) {
|
|
272
|
+
this.sendResponse(clientId, message.id, true, { element: result.data });
|
|
273
|
+
} else {
|
|
274
|
+
this.sendResponse(clientId, message.id, false, void 0, result.error);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Handle getSnapshot message
|
|
279
|
+
*/
|
|
280
|
+
async handleGetSnapshot(clientId, message) {
|
|
281
|
+
const result = await this.handlers.getControlSnapshot();
|
|
282
|
+
if (result.success) {
|
|
283
|
+
this.sendResponse(clientId, message.id, true, result.data);
|
|
284
|
+
} else {
|
|
285
|
+
this.sendResponse(clientId, message.id, false, void 0, result.error);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Handle executeAction message
|
|
290
|
+
*/
|
|
291
|
+
async handleExecuteAction(clientId, message) {
|
|
292
|
+
const { elementId, action } = message.payload;
|
|
293
|
+
const result = await this.handlers.executeElementAction(elementId, action);
|
|
294
|
+
this.sendResponse(clientId, message.id, result.success, result.data, result.error);
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Handle executeComponentAction message
|
|
298
|
+
*/
|
|
299
|
+
async handleExecuteComponentAction(clientId, message) {
|
|
300
|
+
const { componentId, action, params } = message.payload;
|
|
301
|
+
const result = await this.handlers.executeComponentAction(componentId, { action, params });
|
|
302
|
+
this.sendResponse(clientId, message.id, result.success, result.data, result.error);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Handle executeWorkflow message
|
|
306
|
+
*/
|
|
307
|
+
async handleExecuteWorkflow(clientId, message) {
|
|
308
|
+
const { workflowId, params } = message.payload;
|
|
309
|
+
const result = await this.handlers.runWorkflow(workflowId, { params });
|
|
310
|
+
this.sendResponse(clientId, message.id, result.success, result.data, result.error);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Broadcast event to all subscribed clients
|
|
314
|
+
*/
|
|
315
|
+
broadcastEvent(event) {
|
|
316
|
+
for (const [clientId, client] of this.clients) {
|
|
317
|
+
if (client.subscription.events.size === 0 || client.subscription.events.has(event.type)) {
|
|
318
|
+
const eventData = event.data;
|
|
319
|
+
if (eventData.elementId && client.subscription.elementIds.size > 0 && !client.subscription.elementIds.has(eventData.elementId)) {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (eventData.componentId && client.subscription.componentIds.size > 0 && !client.subscription.componentIds.has(eventData.componentId)) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
this.sendToClient(clientId, {
|
|
326
|
+
id: generateId(),
|
|
327
|
+
type: "event",
|
|
328
|
+
timestamp: Date.now(),
|
|
329
|
+
payload: event
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Send message to specific client
|
|
336
|
+
*/
|
|
337
|
+
sendToClient(clientId, message) {
|
|
338
|
+
const client = this.clients.get(clientId);
|
|
339
|
+
if (!client || client.ws.readyState !== 1) return;
|
|
340
|
+
try {
|
|
341
|
+
client.ws.send(JSON.stringify(message));
|
|
342
|
+
if (this.verbose && message.type !== "pong") {
|
|
343
|
+
this.log(`[WS] ${clientId} <- ${message.type}`);
|
|
344
|
+
}
|
|
345
|
+
} catch (error) {
|
|
346
|
+
console.error(`Failed to send message to ${clientId}:`, error);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Send response message
|
|
351
|
+
*/
|
|
352
|
+
sendResponse(clientId, requestId, success, data, error) {
|
|
353
|
+
this.sendToClient(clientId, {
|
|
354
|
+
id: generateId(),
|
|
355
|
+
type: "response",
|
|
356
|
+
timestamp: Date.now(),
|
|
357
|
+
requestId,
|
|
358
|
+
payload: {
|
|
359
|
+
success,
|
|
360
|
+
data,
|
|
361
|
+
error
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Send error message
|
|
367
|
+
*/
|
|
368
|
+
sendError(clientId, requestId, code, message) {
|
|
369
|
+
this.sendToClient(clientId, {
|
|
370
|
+
id: generateId(),
|
|
371
|
+
type: "error",
|
|
372
|
+
timestamp: Date.now(),
|
|
373
|
+
requestId,
|
|
374
|
+
payload: {
|
|
375
|
+
code,
|
|
376
|
+
message
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Get connected client count
|
|
382
|
+
*/
|
|
383
|
+
get clientCount() {
|
|
384
|
+
return this.clients.size;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Get all connected client IDs
|
|
388
|
+
*/
|
|
389
|
+
get clientIds() {
|
|
390
|
+
return Array.from(this.clients.keys());
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Disconnect all clients
|
|
394
|
+
*/
|
|
395
|
+
disconnectAll() {
|
|
396
|
+
for (const [_clientId, client] of this.clients) {
|
|
397
|
+
try {
|
|
398
|
+
client.ws.close();
|
|
399
|
+
} catch {
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
this.clients.clear();
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
// src/server/standalone.ts
|
|
407
|
+
var DEFAULT_CONFIG = {
|
|
408
|
+
host: "localhost",
|
|
409
|
+
port: 9876,
|
|
410
|
+
websocket: false,
|
|
411
|
+
websocketPort: 9876,
|
|
412
|
+
log: console.log
|
|
413
|
+
};
|
|
414
|
+
function wrapError(error, code) {
|
|
415
|
+
return {
|
|
416
|
+
success: false,
|
|
417
|
+
error: typeof error === "string" ? error : error.message,
|
|
418
|
+
code,
|
|
419
|
+
timestamp: Date.now()
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
var StandaloneServer = class {
|
|
423
|
+
constructor(handlers, config = {}) {
|
|
424
|
+
this.server = null;
|
|
425
|
+
this.wsServer = null;
|
|
426
|
+
this.wsHandler = null;
|
|
427
|
+
this.wsConnections = /* @__PURE__ */ new Set();
|
|
428
|
+
this.handlers = handlers;
|
|
429
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
430
|
+
if (this.config.websocket) {
|
|
431
|
+
this.wsHandler = new UIBridgeWSHandler(handlers, {
|
|
432
|
+
verbose: true,
|
|
433
|
+
log: this.config.log
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Start the server
|
|
439
|
+
*/
|
|
440
|
+
async start() {
|
|
441
|
+
const http = await import('http');
|
|
442
|
+
this.server = http.createServer(async (req, res) => {
|
|
443
|
+
await this.handleRequest(req, res);
|
|
444
|
+
});
|
|
445
|
+
if (this.config.websocket && this.wsHandler) {
|
|
446
|
+
await this.startWebSocketServer();
|
|
447
|
+
}
|
|
448
|
+
return new Promise((resolve, reject) => {
|
|
449
|
+
this.server.listen(this.config.port, this.config.host, () => {
|
|
450
|
+
this.config.log(
|
|
451
|
+
`UI Bridge server listening on http://${this.config.host}:${this.config.port}`
|
|
452
|
+
);
|
|
453
|
+
if (this.config.websocket) {
|
|
454
|
+
const wsPort = this.config.websocketPort || this.config.port;
|
|
455
|
+
this.config.log(
|
|
456
|
+
`UI Bridge WebSocket server listening on ws://${this.config.host}:${wsPort}`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
resolve();
|
|
460
|
+
});
|
|
461
|
+
this.server.on("error", reject);
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Start WebSocket server
|
|
466
|
+
*/
|
|
467
|
+
async startWebSocketServer() {
|
|
468
|
+
try {
|
|
469
|
+
const { WebSocketServer } = await import('ws');
|
|
470
|
+
const wsPort = this.config.websocketPort || this.config.port;
|
|
471
|
+
const useSamePort = wsPort === this.config.port;
|
|
472
|
+
if (useSamePort && this.server) {
|
|
473
|
+
this.wsServer = new WebSocketServer({ server: this.server });
|
|
474
|
+
} else {
|
|
475
|
+
this.wsServer = new WebSocketServer({
|
|
476
|
+
host: this.config.host,
|
|
477
|
+
port: wsPort
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
const wss = this.wsServer;
|
|
481
|
+
wss.on("connection", (ws) => {
|
|
482
|
+
this.wsConnections.add(ws);
|
|
483
|
+
this.wsHandler.handleConnection(ws);
|
|
484
|
+
ws.onclose = () => {
|
|
485
|
+
this.wsConnections.delete(ws);
|
|
486
|
+
};
|
|
487
|
+
});
|
|
488
|
+
wss.on("error", (error) => {
|
|
489
|
+
this.config.log(`WebSocket server error: ${error.message}`);
|
|
490
|
+
});
|
|
491
|
+
} catch (error) {
|
|
492
|
+
this.config.log(
|
|
493
|
+
'Warning: WebSocket support requires the "ws" package. Install it with: npm install ws'
|
|
494
|
+
);
|
|
495
|
+
this.wsHandler = null;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Stop the server
|
|
500
|
+
*/
|
|
501
|
+
async stop() {
|
|
502
|
+
if (this.wsHandler) {
|
|
503
|
+
this.wsHandler.disconnectAll();
|
|
504
|
+
}
|
|
505
|
+
if (this.wsServer) {
|
|
506
|
+
const wss = this.wsServer;
|
|
507
|
+
wss.close();
|
|
508
|
+
this.wsServer = null;
|
|
509
|
+
}
|
|
510
|
+
for (const ws of this.wsConnections) {
|
|
511
|
+
ws.close();
|
|
512
|
+
}
|
|
513
|
+
this.wsConnections.clear();
|
|
514
|
+
return new Promise((resolve, reject) => {
|
|
515
|
+
if (!this.server) {
|
|
516
|
+
resolve();
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
this.server.close((err) => {
|
|
520
|
+
if (err) reject(err);
|
|
521
|
+
else resolve();
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Handle an HTTP request
|
|
527
|
+
*/
|
|
528
|
+
async handleRequest(req, res) {
|
|
529
|
+
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
530
|
+
const method = req.method || "GET";
|
|
531
|
+
const basePath = this.config.basePath || "/ui-bridge";
|
|
532
|
+
if (this.config.cors) {
|
|
533
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
534
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
|
535
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
536
|
+
}
|
|
537
|
+
if (method === "OPTIONS") {
|
|
538
|
+
res.writeHead(204);
|
|
539
|
+
res.end();
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (url.pathname === "/health") {
|
|
543
|
+
this.sendJSON(res, { status: "ok", timestamp: Date.now() });
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
let path = url.pathname;
|
|
547
|
+
if (path.startsWith(basePath)) {
|
|
548
|
+
path = path.slice(basePath.length) || "/";
|
|
549
|
+
}
|
|
550
|
+
const route = this.findRoute(path, method);
|
|
551
|
+
if (!route) {
|
|
552
|
+
this.sendJSON(res, wrapError("Not found", "NOT_FOUND"), 404);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
let body = {};
|
|
557
|
+
if (method === "POST") {
|
|
558
|
+
body = await this.parseBody(req);
|
|
559
|
+
}
|
|
560
|
+
const params = this.extractParams(path, route.path);
|
|
561
|
+
const handlerName = route.handler;
|
|
562
|
+
const handler = this.handlers[handlerName];
|
|
563
|
+
if (!handler) {
|
|
564
|
+
this.sendJSON(res, wrapError("Not implemented", "NOT_IMPLEMENTED"), 501);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const args = [];
|
|
568
|
+
if (route.params) {
|
|
569
|
+
for (const param of route.params) {
|
|
570
|
+
args.push(params[param]);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
if (method === "POST") {
|
|
574
|
+
args.push(body);
|
|
575
|
+
}
|
|
576
|
+
if (method === "GET") {
|
|
577
|
+
const query = Object.fromEntries(url.searchParams);
|
|
578
|
+
if (Object.keys(query).length > 0) {
|
|
579
|
+
args.push(query);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
const result = await handler(
|
|
583
|
+
...args
|
|
584
|
+
);
|
|
585
|
+
this.sendJSON(res, result);
|
|
586
|
+
} catch (error) {
|
|
587
|
+
this.config.log(`Error handling ${method} ${path}: ${error}`);
|
|
588
|
+
this.sendJSON(res, wrapError(error, "INTERNAL_ERROR"), 500);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Find a matching route
|
|
593
|
+
*/
|
|
594
|
+
findRoute(path, method) {
|
|
595
|
+
for (const route of UI_BRIDGE_ROUTES) {
|
|
596
|
+
if (route.method !== method) continue;
|
|
597
|
+
const routeRegex = route.path.replace(/:[^/]+/g, "([^/]+)").replace(/\//g, "\\/");
|
|
598
|
+
const regex = new RegExp(`^${routeRegex}$`);
|
|
599
|
+
if (regex.test(path)) {
|
|
600
|
+
return route;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Extract params from path
|
|
607
|
+
*/
|
|
608
|
+
extractParams(path, routePath) {
|
|
609
|
+
const params = {};
|
|
610
|
+
const routeParts = routePath.split("/");
|
|
611
|
+
const pathParts = path.split("/");
|
|
612
|
+
for (let i = 0; i < routeParts.length; i++) {
|
|
613
|
+
if (routeParts[i].startsWith(":")) {
|
|
614
|
+
params[routeParts[i].slice(1)] = pathParts[i];
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return params;
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Parse request body
|
|
621
|
+
*/
|
|
622
|
+
parseBody(req) {
|
|
623
|
+
return new Promise((resolve, reject) => {
|
|
624
|
+
let data = "";
|
|
625
|
+
req.on("data", (chunk) => data += chunk);
|
|
626
|
+
req.on("end", () => {
|
|
627
|
+
try {
|
|
628
|
+
resolve(data ? JSON.parse(data) : {});
|
|
629
|
+
} catch {
|
|
630
|
+
resolve({});
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
req.on("error", reject);
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Send JSON response
|
|
638
|
+
*/
|
|
639
|
+
sendJSON(res, data, status = 200) {
|
|
640
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
641
|
+
res.end(JSON.stringify(data));
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Broadcast a message to all WebSocket connections (legacy)
|
|
645
|
+
*/
|
|
646
|
+
broadcast(message) {
|
|
647
|
+
const data = JSON.stringify(message);
|
|
648
|
+
for (const ws of this.wsConnections) {
|
|
649
|
+
if (ws.readyState === 1) {
|
|
650
|
+
ws.send(data);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Broadcast an event to all subscribed WebSocket clients
|
|
656
|
+
*/
|
|
657
|
+
broadcastEvent(event) {
|
|
658
|
+
if (this.wsHandler) {
|
|
659
|
+
this.wsHandler.broadcastEvent(event);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Get WebSocket handler for direct access
|
|
664
|
+
*/
|
|
665
|
+
getWSHandler() {
|
|
666
|
+
return this.wsHandler;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Get number of connected WebSocket clients
|
|
670
|
+
*/
|
|
671
|
+
get wsClientCount() {
|
|
672
|
+
return this.wsHandler?.clientCount ?? 0;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Get the server address
|
|
676
|
+
*/
|
|
677
|
+
getAddress() {
|
|
678
|
+
const address = this.server?.address();
|
|
679
|
+
if (!address || typeof address === "string") return null;
|
|
680
|
+
return { host: this.config.host, port: address.port };
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
async function createStandaloneServer(handlers, config) {
|
|
684
|
+
const server = new StandaloneServer(handlers, config);
|
|
685
|
+
await server.start();
|
|
686
|
+
return server;
|
|
687
|
+
}
|
|
688
|
+
async function startCLI(handlers, args = process.argv.slice(2)) {
|
|
689
|
+
const config = {};
|
|
690
|
+
for (let i = 0; i < args.length; i++) {
|
|
691
|
+
const arg = args[i];
|
|
692
|
+
const nextArg = args[i + 1];
|
|
693
|
+
if (arg === "--port" || arg === "-p") {
|
|
694
|
+
config.port = parseInt(nextArg);
|
|
695
|
+
i++;
|
|
696
|
+
} else if (arg === "--host" || arg === "-h") {
|
|
697
|
+
config.host = nextArg;
|
|
698
|
+
i++;
|
|
699
|
+
} else if (arg === "--cors") {
|
|
700
|
+
config.cors = true;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
const server = await createStandaloneServer(handlers, config);
|
|
704
|
+
process.on("SIGINT", async () => {
|
|
705
|
+
console.log("\nShutting down...");
|
|
706
|
+
await server.stop();
|
|
707
|
+
process.exit(0);
|
|
708
|
+
});
|
|
709
|
+
process.on("SIGTERM", async () => {
|
|
710
|
+
await server.stop();
|
|
711
|
+
process.exit(0);
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
exports.StandaloneServer = StandaloneServer;
|
|
716
|
+
exports.createStandaloneServer = createStandaloneServer;
|
|
717
|
+
exports.startCLI = startCLI;
|
|
718
|
+
//# sourceMappingURL=standalone.js.map
|
|
719
|
+
//# sourceMappingURL=standalone.js.map
|