diff --git a/src/Config.zig b/src/Config.zig index c4099c97..30692516 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -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, }; } diff --git a/src/browser/js/Local.zig b/src/browser/js/Local.zig index a4757221..63a0fe4e 100644 --- a/src/browser/js/Local.zig +++ b/src/browser/js/Local.zig @@ -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 = {} }; diff --git a/src/browser/js/Value.zig b/src/browser/js/Value.zig index f80727b0..bf1696e7 100644 --- a/src/browser/js/Value.zig +++ b/src/browser/js/Value.zig @@ -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); } diff --git a/src/browser/webapi/net/WebSocket.zig b/src/browser/webapi/net/WebSocket.zig index 32be22fb..6869cac3 100644 --- a/src/browser/webapi/net/WebSocket.zig +++ b/src/browser/webapi/net/WebSocket.zig @@ -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");