Merge pull request #2158 from lightpanda-io/websocket_tweaks

WS.close returns DOMException
This commit is contained in:
Karl Seguin
2026-04-15 07:08:37 +08:00
committed by GitHub
4 changed files with 63 additions and 36 deletions

View File

@@ -130,7 +130,7 @@ pub fn httpMaxResponseSize(self: *const Config) ?usize {
pub fn wsMaxConcurrent(self: *const Config) u8 {
return switch (self.mode) {
inline .serve, .fetch, .mcp => |opts| opts.common.ws_max_concurrent orelse 8,
inline .serve, .fetch, .mcp => |opts| opts.common.ws_max_concurrent orelse 64,
else => unreachable,
};
}

View File

@@ -857,6 +857,18 @@ fn jsValueToTypedArray(comptime T: type, js_val: js.Value) !?[]T {
return ptr[0..num_elements];
}
},
f32 => {
if (js_val.isFloat32Array()) {
const ptr = @as([*]f32, @ptrCast(@alignCast(base)));
return ptr[0..num_elements];
}
},
f64 => {
if (js_val.isFloat64Array()) {
const ptr = @as([*]f64, @ptrCast(@alignCast(base)));
return ptr[0..num_elements];
}
},
else => {},
}
return error.InvalidArgument;
@@ -985,6 +997,12 @@ fn probeJsValueToZig(self: *const Local, comptime T: type, js_val: js.Value) !Pr
i64 => if (js_val.isBigInt64Array()) {
return .{ .ok = {} };
},
f32 => if (js_val.isFloat32Array()) {
return .{ .ok = {} };
},
f64 => if (js_val.isFloat64Array()) {
return .{ .ok = {} };
},
else => {},
}
return .{ .invalid = {} };

View File

@@ -155,6 +155,14 @@ pub fn isBigInt64Array(self: Value) bool {
return v8.v8__Value__IsBigInt64Array(self.handle);
}
pub fn isFloat32Array(self: Value) bool {
return v8.v8__Value__IsFloat32Array(self.handle);
}
pub fn isFloat64Array(self: Value) bool {
return v8.v8__Value__IsFloat64Array(self.handle);
}
pub fn isPromise(self: Value) bool {
return v8.v8__Value__IsPromise(self.handle);
}

View File

@@ -88,20 +88,6 @@ pub const BinaryType = enum {
arraybuffer,
};
fn isValidProtocol(protocol: []const u8) bool {
if (protocol.len == 0) return false;
for (protocol) |c| {
// Control characters
if (c <= 31 or c == 127) return false;
// Separators per RFC 2616
switch (c) {
'(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t' => return false,
else => {},
}
}
return true;
}
pub fn init(url: []const u8, protocols: [][]const u8, page: *Page) !*WebSocket {
{
if (url.len < 6) {
@@ -196,6 +182,18 @@ pub fn deinit(self: *WebSocket, session: *Session) void {
session.releaseArena(self._arena);
}
pub fn releaseRef(self: *WebSocket, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *WebSocket) void {
self._rc.acquire();
}
fn asEventTarget(self: *WebSocket) *EventTarget {
return self._proto;
}
// we're being aborted internally (e.g. page shutting down)
pub fn kill(self: *WebSocket) void {
self.cleanup();
@@ -211,13 +209,15 @@ pub fn disconnected(self: *WebSocket, err_: ?anyerror) void {
log.info(.websocket, "disconnected", .{ .url = self._url, .reason = "closed" });
}
self.cleanup();
defer self.cleanup();
// Use 1006 (abnormal closure) if connection wasn't cleanly closed
const code = if (was_clean) self._close_code else 1006;
const reason = if (was_clean) self._close_reason else "";
// Spec requires error event before close on abnormal closure
// Spec requires error event before close on abnormal closure.
// Dispatch events before cleanup since cleanup releases the ref count
// which may free our event handler references.
if (!was_clean) {
self.dispatchErrorEvent() catch |err| {
log.err(.websocket, "error event dispatch failed", .{ .err = err });
@@ -239,18 +239,6 @@ fn cleanup(self: *WebSocket) void {
}
}
pub fn releaseRef(self: *WebSocket, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *WebSocket) void {
self._rc.acquire();
}
fn asEventTarget(self: *WebSocket) *EventTarget {
return self._proto;
}
fn queueMessage(self: *WebSocket, msg: Message) !void {
const was_empty = self._send_queue.items.len == 0;
try self._send_queue.append(self._arena, msg);
@@ -263,6 +251,20 @@ fn queueMessage(self: *WebSocket, msg: Message) !void {
}
}
fn isValidProtocol(protocol: []const u8) bool {
if (protocol.len == 0) return false;
for (protocol) |c| {
// Control characters and non-ASCII
if (c <= 31 or c >= 127) return false;
// Separators per RFC 2616
switch (c) {
'(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t' => return false,
else => {},
}
}
return true;
}
/// WebSocket send() accepts string, Blob, ArrayBuffer, or TypedArray
const SendData = union(enum) {
blob: *Blob,
@@ -279,17 +281,16 @@ const BinaryData = union(enum) {
uint32: []u32,
int64: []i64,
uint64: []u64,
float32: []f32,
float64: []f64,
fn asBuffer(self: BinaryData) []u8 {
return switch (self) {
.int8 => |b| @as([*]u8, @ptrCast(b.ptr))[0..b.len],
.uint8 => |b| b,
.int16 => |b| @as([*]u8, @ptrCast(b.ptr))[0 .. b.len * 2],
.uint16 => |b| @as([*]u8, @ptrCast(b.ptr))[0 .. b.len * 2],
.int32 => |b| @as([*]u8, @ptrCast(b.ptr))[0 .. b.len * 4],
.uint32 => |b| @as([*]u8, @ptrCast(b.ptr))[0 .. b.len * 4],
.int64 => |b| @as([*]u8, @ptrCast(b.ptr))[0 .. b.len * 8],
.uint64 => |b| @as([*]u8, @ptrCast(b.ptr))[0 .. b.len * 8],
inline .int16, .uint16 => |b| @as([*]u8, @ptrCast(b.ptr))[0 .. b.len * 2],
inline .int32, .uint32, .float32 => |b| @as([*]u8, @ptrCast(b.ptr))[0 .. b.len * 4],
inline .int64, .uint64, .float64 => |b| @as([*]u8, @ptrCast(b.ptr))[0 .. b.len * 8],
};
}
};
@@ -754,7 +755,7 @@ pub const JsApi = struct {
pub const onclose = bridge.accessor(WebSocket.getOnClose, WebSocket.setOnClose, .{});
pub const send = bridge.function(WebSocket.send, .{ .dom_exception = true });
pub const close = bridge.function(WebSocket.close, .{});
pub const close = bridge.function(WebSocket.close, .{ .dom_exception = true });
};
const testing = @import("../../../testing.zig");