Skip to content

Commit 8e771c5

Browse files
committed
Harden WebUI API lifecycle
1 parent 7e62b2c commit 8e771c5

2 files changed

Lines changed: 6 additions & 98 deletions

File tree

docs/upstream_file_parity.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Implemented capability groups:
5050
- Raw channel send/receive.
5151
- Public/private bind policy equivalent (`AppOptions.public_network`).
5252
- Proxy launch option equivalent (`BrowserLaunchOptions.proxy_server`).
53-
- Script command queue + response routing (`/webui/script/pull`, `/webui/script/response`) with modern `runScript`/`evalScript`.
53+
- Script command queue + push dispatch (`/webui/ws`) with response routing (`/webui/script/response`) for modern `runScript`/`evalScript`.
5454
- TLS runtime state API (`TlsOptions`, `App.setTlsCertificate`, `App.tlsInfo`) with certificate ingest and generated fallback material.
5555

5656
Remaining gaps versus full `webui.c` behavior:

src/root.zig

Lines changed: 5 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,17 +1810,6 @@ const WindowState = struct {
18101810
return false;
18111811
}
18121812

1813-
fn dequeueScriptForConnectionLocked(self: *WindowState, connection_id: usize) ?*ScriptTask {
1814-
for (self.script_pending.items, 0..) |task, idx| {
1815-
if (task.target_connection) |target| {
1816-
if (target != connection_id) continue;
1817-
}
1818-
_ = self.script_pending.orderedRemove(idx);
1819-
return task;
1820-
}
1821-
return null;
1822-
}
1823-
18241813
fn completeScriptTaskLocked(
18251814
self: *WindowState,
18261815
task_id: u64,
@@ -3430,7 +3419,6 @@ fn handleConnection(state: *WindowState, allocator: std.mem.Allocator, stream: s
34303419
if (try handleRpcJobRoute(state, allocator, stream, request.method, request.path, path_only)) return false;
34313420
if (try handleRpcJobCancelRoute(state, allocator, stream, request.method, path_only, request.body)) return false;
34323421
if (try handleLifecycleRoute(state, allocator, stream, request.method, path_only, request.headers, request.body)) return false;
3433-
if (try handleScriptPullRoute(state, allocator, stream, request.method, path_only, request.headers)) return false;
34343422
if (try handleScriptResponseRoute(state, allocator, stream, request.method, path_only, request.body)) return false;
34353423
if (try handleWindowControlRoute(state, allocator, stream, request.method, path_only, request.body)) return false;
34363424
if (try handleWindowStyleRoute(state, allocator, stream, request.method, path_only, request.body)) return false;
@@ -3485,64 +3473,6 @@ fn handleLifecycleRoute(
34853473
return true;
34863474
}
34873475

3488-
fn handleScriptPullRoute(
3489-
state: *WindowState,
3490-
allocator: std.mem.Allocator,
3491-
stream: std.net.Stream,
3492-
method: []const u8,
3493-
path_only: []const u8,
3494-
headers: []const u8,
3495-
) !bool {
3496-
if (!std.mem.eql(u8, method, "GET")) return false;
3497-
if (!std.mem.eql(u8, path_only, "/webui/script/pull")) return false;
3498-
3499-
const client_token = httpHeaderValue(headers, "x-webui-client-id") orelse default_client_token;
3500-
3501-
state.state_mutex.lock();
3502-
const client_ref = state.findOrCreateClientSessionLocked(client_token) catch {
3503-
state.state_mutex.unlock();
3504-
try writeHttpResponse(stream, 500, "application/json; charset=utf-8", "{\"error\":\"client_session_failed\"}");
3505-
return true;
3506-
};
3507-
const task = state.dequeueScriptForConnectionLocked(client_ref.connection_id);
3508-
if (task) |work| {
3509-
if (work.expect_result) {
3510-
state.script_inflight.append(work) catch {
3511-
state.state_mutex.unlock();
3512-
try writeHttpResponse(stream, 500, "application/json; charset=utf-8", "{\"error\":\"script_queue_failed\"}");
3513-
return true;
3514-
};
3515-
}
3516-
}
3517-
state.state_mutex.unlock();
3518-
3519-
if (task == null) {
3520-
try writeHttpResponse(stream, 204, "application/json; charset=utf-8", "");
3521-
return true;
3522-
}
3523-
3524-
const work = task.?;
3525-
const payload = try std.json.Stringify.valueAlloc(allocator, .{
3526-
.id = work.id,
3527-
.script = work.script,
3528-
.expect_result = work.expect_result,
3529-
.client_id = client_ref.client_id,
3530-
.connection_id = client_ref.connection_id,
3531-
}, .{});
3532-
defer allocator.free(payload);
3533-
3534-
if (!work.expect_result) {
3535-
work.mutex.lock();
3536-
work.done = true;
3537-
work.cond.signal();
3538-
work.mutex.unlock();
3539-
work.deinit();
3540-
}
3541-
3542-
try writeHttpResponse(stream, 200, "application/json; charset=utf-8", payload);
3543-
return true;
3544-
}
3545-
35463476
fn handleScriptResponseRoute(
35473477
state: *WindowState,
35483478
allocator: std.mem.Allocator,
@@ -5055,7 +4985,7 @@ test "evalScript times out when no client is polling" {
50554985
try std.testing.expect(result.value == null);
50564986
}
50574987

5058-
test "script pull and response routes complete queued eval task" {
4988+
test "script response route completes queued eval task" {
50594989
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
50604990
defer _ = gpa.deinit();
50614991

@@ -5071,37 +5001,15 @@ test "script pull and response routes complete queued eval task" {
50715001
const state = win.state();
50725002
state.state_mutex.lock();
50735003
const task = try state.queueScriptLocked(gpa.allocator(), "return 6 * 7;", null, true);
5004+
const moved = state.removeScriptPendingLocked(task);
5005+
try std.testing.expect(moved);
5006+
try state.script_inflight.append(task);
50745007
state.state_mutex.unlock();
50755008

5076-
const pull_response = try httpRoundTripWithHeaders(
5077-
gpa.allocator(),
5078-
state.server_port,
5079-
"GET",
5080-
"/webui/script/pull",
5081-
null,
5082-
&.{"x-webui-client-id: script-test-client"},
5083-
);
5084-
defer gpa.allocator().free(pull_response);
5085-
try std.testing.expect(std.mem.indexOf(u8, pull_response, "HTTP/1.1 200 OK") != null);
5086-
try std.testing.expect(std.mem.indexOf(u8, pull_response, "\"expect_result\":true") != null);
5087-
try std.testing.expect(std.mem.indexOf(u8, pull_response, "return 6 * 7;") != null);
5088-
5089-
const body = httpResponseBody(pull_response);
5090-
var parsed = try std.json.parseFromSlice(std.json.Value, gpa.allocator(), body, .{});
5091-
defer parsed.deinit();
5092-
try std.testing.expect(parsed.value == .object);
5093-
const id_value = parsed.value.object.get("id") orelse return error.InvalidHttpRequest;
5094-
const pulled_id: u64 = switch (id_value) {
5095-
.integer => |v| @as(u64, @intCast(v)),
5096-
.float => |v| @as(u64, @intFromFloat(v)),
5097-
else => return error.InvalidHttpRequest,
5098-
};
5099-
try std.testing.expectEqual(task.id, pulled_id);
5100-
51015009
const completion_body = try std.fmt.allocPrint(
51025010
gpa.allocator(),
51035011
"{{\"id\":{d},\"js_error\":false,\"value\":42}}",
5104-
.{pulled_id},
5012+
.{task.id},
51055013
);
51065014
defer gpa.allocator().free(completion_body);
51075015

0 commit comments

Comments
 (0)