diff options
author | 2022-10-15 03:06:41 -0700 | |
---|---|---|
committer | 2022-10-15 03:06:41 -0700 | |
commit | 4b5af13ac09e32e8e446cb8000fe5d8d101b3afb (patch) | |
tree | d7431bfea4723717fe4a2f6db89b2a97d017f201 | |
parent | 35cbfa63a6e54d37ad5edb14ef2c025acf7bbddd (diff) | |
download | bun-4b5af13ac09e32e8e446cb8000fe5d8d101b3afb.tar.gz bun-4b5af13ac09e32e8e446cb8000fe5d8d101b3afb.tar.zst bun-4b5af13ac09e32e8e446cb8000fe5d8d101b3afb.zip |
WebSocket Server support
-rw-r--r-- | build-id | 2 | ||||
-rw-r--r-- | src/bun.js/api/server.classes.ts | 78 | ||||
-rw-r--r-- | src/bun.js/api/server.zig | 1261 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h | 3 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h | 3 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h | 6 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h | 7 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses.cpp | 490 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses.h | 125 | ||||
-rw-r--r-- | src/bun.js/bindings/generated_classes.zig | 198 | ||||
-rw-r--r-- | src/bun.js/bindings/generated_classes_list.zig | 1 | ||||
-rw-r--r-- | src/bun.js/bindings/headers-cpp.h | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.h | 2 | ||||
-rw-r--r-- | src/deps/_libusockets.h | 10 | ||||
-rw-r--r-- | src/deps/libuwsockets.cpp | 29 | ||||
-rw-r--r-- | src/deps/uws.zig | 441 | ||||
-rw-r--r-- | src/jsc.zig | 1 |
17 files changed, 2430 insertions, 229 deletions
@@ -1 +1 @@ -0
\ No newline at end of file +1
\ No newline at end of file diff --git a/src/bun.js/api/server.classes.ts b/src/bun.js/api/server.classes.ts new file mode 100644 index 000000000..5ae4f1739 --- /dev/null +++ b/src/bun.js/api/server.classes.ts @@ -0,0 +1,78 @@ +import { define } from "../scripts/class-definitions"; + +function generate(name) { + return define({ + name, + proto: { + fetch: { + fn: "fetch", + length: 1, + }, + }, + values: ["callback"], + klass: {}, + finalize: true, + construct: true, + }); +} +export default [ + // generate(`HTTPServer`), + // generate(`DebugModeHTTPServer`), + // generate(`HTTPSServer`), + // generate(`DebugModeHTTPSServer`), + + define({ + name: "ServerWebSocket", + JSType: "0b11101110", + proto: { + send: { + fn: "send", + length: 2, + }, + close: { + fn: "close", + length: 1, + }, + getBufferedAmount: { + fn: "getBufferedAmount", + length: 0, + }, + publish: { + fn: "publish", + length: 3, + }, + data: { + getter: "getData", + cache: true, + setter: "setData", + }, + readyState: { + getter: "getReadyState", + }, + subscribe: { + fn: "subscribe", + length: 1, + }, + unsubscribe: { + fn: "unsubscribe", + length: 1, + }, + isSubscribed: { + fn: "isSubscribed", + length: 1, + }, + + // topics: { + // getter: "getTopics", + // }, + + remoteAddress: { + getter: "getRemoteAddress", + cache: true, + }, + }, + finalize: true, + construct: true, + klass: {}, + }), +]; diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index 11400dde5..9b26f4b11 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -109,6 +109,9 @@ pub const ServerConfig = struct { onError: JSC.JSValue = JSC.JSValue.zero, onRequest: JSC.JSValue = JSC.JSValue.zero, + websockets: WebSocketServer.List = .{}, + websocket: WebSocketServer = .{}, + pub const SSLConfig = struct { server_name: [*c]const u8 = null, @@ -274,12 +277,89 @@ pub const ServerConfig = struct { args.base_uri = origin; } + var websockets = WebSocketServer.List{}; + if (arguments.next()) |arg| { if (arg.isUndefinedOrNull() or !arg.isObject()) { JSC.throwInvalidArguments("Bun.serve expects an object", .{}, global, exception); return args; } + if (arg.getTruthy(global, "webSocket") orelse arg.getTruthy(global, "websocket")) |websocket_object| { + if (!websocket_object.isObject()) { + JSC.throwInvalidArguments("Expected webSocket to be an object", .{}, global, exception); + if (args.ssl_config) |*conf| { + conf.deinit(); + } + return args; + } + + if (WebSocketServer.onCreate(global, websocket_object)) |wss| { + args.websocket = wss; + } else { + if (args.ssl_config) |*conf| { + conf.deinit(); + } + return args; + } + } + + if (arg.getTruthy(global, "webSockets") orelse arg.getTruthy(global, "websockets")) |websocket_object| { + if (!websocket_object.isObject()) { + JSC.throwInvalidArguments("Expected webSockets to be an object", .{}, global, exception); + if (args.ssl_config) |*conf| { + conf.deinit(); + } + return args; + } + + var property_names = JSC.JSPropertyIterator(.{ + .include_value = true, + .skip_empty_name = true, + }).init(global, websocket_object.asObjectRef()); + + defer property_names.deinit(); + websockets.ensureTotalCapacity(bun.default_allocator, property_names.len) catch unreachable; + while (property_names.next()) |name| { + var str = name.toSlice(bun.default_allocator); + defer str.deinit(); + const slice = str.slice(); + if (slice.len == 0) continue; + if (slice[0] != '/') { + JSC.throwInvalidArguments("Expected webSocket path to start with /", .{}, global, exception); + if (args.ssl_config) |*conf| { + conf.deinit(); + } + return args; + } + + const object = property_names.value; + if (object.isEmptyOrUndefinedOrNull() or !object.isObject()) { + JSC.throwInvalidArguments("Expected webSocket to be an object", .{}, global, exception); + if (args.ssl_config) |*conf| { + conf.deinit(); + } + websockets.deinit(bun.default_allocator); + return args; + } + + const handler = WebSocketServer.onCreate(global, object) orelse { + if (args.ssl_config) |*conf| { + conf.deinit(); + } + websockets.deinit(bun.default_allocator); + return args; + }; + + websockets.putAssumeCapacity( + bun.span(bun.default_allocator.dupeZ(u8, slice) catch unreachable), + handler, + ); + } + + args.websockets = websockets; + } + if (arg.getTruthy(global, "port")) |port_| { args.port = @intCast(u16, @minimum(@maximum(0, port_.toInt32()), std.math.maxInt(u16))); } @@ -1386,6 +1466,118 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp const streamLog = Output.scoped(.ReadableStream, false); + pub fn onResponse( + ctx: *RequestContext, + this: *ThisServer, + req: *uws.Request, + request_object: *Request, + request_value: JSValue, + response_value: JSValue, + ) void { + request_value.ensureStillAlive(); + response_value.ensureStillAlive(); + + if (ctx.aborted) { + ctx.finalizeForAbort(); + return; + } + if (response_value.isEmptyOrUndefinedOrNull() and !ctx.resp.hasResponded()) { + ctx.renderMissing(); + return; + } + + if (response_value.isError() or response_value.isAggregateError(this.globalThis) or response_value.isException(this.globalThis.vm())) { + ctx.runErrorHandler(response_value); + + return; + } + + if (response_value.as(JSC.WebCore.Response)) |response| { + ctx.response_jsvalue = response_value; + ctx.response_jsvalue.ensureStillAlive(); + ctx.response_protected = false; + switch (response.body.value) { + .Blob => |*blob| { + if (blob.needsToReadFile()) { + response_value.protect(); + ctx.response_protected = true; + } + }, + .Locked => { + response_value.protect(); + ctx.response_protected = true; + }, + else => {}, + } + ctx.render(response); + return; + } + + var wait_for_promise = false; + var vm = this.vm; + + if (response_value.asPromise()) |promise| { + // If we immediately have the value available, we can skip the extra event loop tick + switch (promise.status(vm.global.vm())) { + .Pending => {}, + .Fulfilled => { + ctx.handleResolve(promise.result(vm.global.vm())); + return; + }, + .Rejected => { + ctx.handleReject(promise.result(vm.global.vm())); + return; + }, + } + wait_for_promise = true; + // I don't think this case should happen + // But I'm uncertain + } else if (response_value.asInternalPromise()) |promise| { + switch (promise.status(vm.global.vm())) { + .Pending => {}, + .Fulfilled => { + ctx.handleResolve(promise.result(vm.global.vm())); + return; + }, + .Rejected => { + ctx.handleReject(promise.result(vm.global.vm())); + return; + }, + } + wait_for_promise = true; + } + + if (wait_for_promise) { + request_object.uws_request = req; + + request_object.ensureURL() catch { + request_object.url = ""; + }; + + // we have to clone the request headers here since they will soon belong to a different request + if (request_object.headers == null) { + request_object.headers = JSC.FetchHeaders.createFromUWS(this.globalThis, req); + } + + if (comptime debug_mode) { + ctx.pathname = bun.default_allocator.dupe(u8, request_object.url) catch unreachable; + } + + // This object dies after the stack frame is popped + // so we have to clear it in here too + request_object.uws_request = null; + + ctx.setAbortHandler(); + ctx.pending_promises_for_abort += 1; + + response_value.then(this.globalThis, ctx, RequestContext.onResolve, RequestContext.onReject); + return; + } + + // The user returned something that wasn't a promise or a promise with a response + if (!ctx.resp.hasResponded()) ctx.renderMissing(); + } + pub fn handleResolveStream(req: *RequestContext) void { streamLog("onResolve", .{}); var wrote_anything = false; @@ -2060,6 +2252,738 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp }; } +pub const WebSocketServer = struct { + onOpen: JSC.JSValue = .zero, + onUpgrade: JSC.JSValue = .zero, + onMessage: JSC.JSValue = .zero, + onClose: JSC.JSValue = .zero, + onDrain: JSC.JSValue = .zero, + globalObject: *JSC.JSGlobalObject = undefined, + active_connections: usize = 0, + + maxPayloadLength: u32 = 1024 * 1024 * 16, + maxLifetime: u16 = 0, + idleTimeout: u16 = 120, + compression: i32 = 0, + backpressureLimit: u32 = 1024 * 1024 * 16, + sendPingsAutomatically: bool = true, + resetIdleTimeoutOnSend: bool = true, + closeOnBackpressureLimit: bool = false, + + pub fn toBehavior(this: WebSocketServer) uws.WebSocketBehavior { + return .{ + .maxPayloadLength = this.maxPayloadLength, + .idleTimeout = this.idleTimeout, + .compression = this.compression, + .maxBackpressure = this.backpressureLimit, + .sendPingsAutomatically = this.sendPingsAutomatically, + .maxLifetime = this.maxLifetime, + .resetIdleTimeoutOnSend = this.resetIdleTimeoutOnSend, + .closeOnBackpressureLimit = this.closeOnBackpressureLimit, + }; + } + + pub fn protect(this: WebSocketServer) void { + this.onUpgrade.protect(); + this.onOpen.protect(); + this.onMessage.protect(); + this.onClose.protect(); + this.onDrain.protect(); + } + + pub fn unprotect(this: WebSocketServer) void { + this.onUpgrade.unprotect(); + this.onOpen.unprotect(); + this.onMessage.unprotect(); + this.onClose.unprotect(); + this.onDrain.unprotect(); + } + + const CompressTable = bun.ComptimeStringMap(i32, .{ + .{ "disable", 0 }, + .{ "shared", uws.SHARED_COMPRESSOR }, + .{ "dedicated", uws.DEDICATED_COMPRESSOR }, + .{ "3KB", uws.DEDICATED_COMPRESSOR_3KB }, + .{ "4KB", uws.DEDICATED_COMPRESSOR_4KB }, + .{ "8KB", uws.DEDICATED_COMPRESSOR_8KB }, + .{ "16KB", uws.DEDICATED_COMPRESSOR_16KB }, + .{ "32KB", uws.DEDICATED_COMPRESSOR_32KB }, + .{ "64KB", uws.DEDICATED_COMPRESSOR_64KB }, + .{ "128KB", uws.DEDICATED_COMPRESSOR_128KB }, + .{ "256KB", uws.DEDICATED_COMPRESSOR_256KB }, + }); + + const DecompressTable = bun.ComptimeStringMap(i32, .{ + .{ "disable", 0 }, + .{ "shared", uws.SHARED_DECOMPRESSOR }, + .{ "dedicated", uws.DEDICATED_DECOMPRESSOR }, + .{ "3KB", uws.DEDICATED_COMPRESSOR_3KB }, + .{ "4KB", uws.DEDICATED_COMPRESSOR_4KB }, + .{ "8KB", uws.DEDICATED_COMPRESSOR_8KB }, + .{ "16KB", uws.DEDICATED_COMPRESSOR_16KB }, + .{ "32KB", uws.DEDICATED_COMPRESSOR_32KB }, + .{ "64KB", uws.DEDICATED_COMPRESSOR_64KB }, + .{ "128KB", uws.DEDICATED_COMPRESSOR_128KB }, + .{ "256KB", uws.DEDICATED_COMPRESSOR_256KB }, + }); + + pub fn onCreate(globalObject: *JSC.JSGlobalObject, object: JSValue) ?WebSocketServer { + if (!object.isObject()) { + globalObject.throwInvalidArguments("websocket expects an options object", .{}); + return null; + } + + var server = WebSocketServer{}; + + if (object.getTruthy(globalObject, "compressor")) |compression| { + if (compression.isBoolean()) { + server.compression |= if (compression.toBoolean()) uws.SHARED_COMPRESSOR else 0; + } else if (compression.isString()) { + var slice = compression.toSlice(globalObject, bun.default_allocator); + defer slice.deinit(); + server.compression |= CompressTable.get(slice.slice()) orelse { + globalObject.throwInvalidArguments( + "websocket expects a valid compressor option, either disable \"shared\" \"dedicated\" \"3KB\" \"4KB\" \"8KB\" \"16KB\" \"32KB\" \"64KB\" \"128KB\" or \"256KB\"", + .{}, + ); + return null; + }; + } else { + globalObject.throwInvalidArguments( + "websocket expects a valid compressor option, either disable \"shared\" \"dedicated\" \"3KB\" \"4KB\" \"8KB\" \"16KB\" \"32KB\" \"64KB\" \"128KB\" or \"256KB\"", + .{}, + ); + return null; + } + } + if (object.getTruthy(globalObject, "decompressor")) |compression| { + if (compression.isBoolean()) { + server.compression |= if (compression.toBoolean()) uws.SHARED_DECOMPRESSOR else 0; + } else if (compression.isString()) { + var slice = compression.toSlice(globalObject, bun.default_allocator); + defer slice.deinit(); + server.compression |= DecompressTable.get(slice.slice()) orelse { + globalObject.throwInvalidArguments( + "websocket expects a valid decompressor option, either \"disable\" \"shared\" \"dedicated\" \"3KB\" \"4KB\" \"8KB\" \"16KB\" \"32KB\" \"64KB\" \"128KB\" or \"256KB\"", + .{}, + ); + return null; + }; + } else { + globalObject.throwInvalidArguments( + "websocket expects a valid decompressor option, either \"disable\" \"shared\" \"dedicated\" \"3KB\" \"4KB\" \"8KB\" \"16KB\" \"32KB\" \"64KB\" \"128KB\" or \"256KB\"", + .{}, + ); + return null; + } + } + + if (object.get(globalObject, "maxPayloadLength")) |value| { + if (!value.isUndefinedOrNull()) { + if (!value.isAnyInt()) { + globalObject.throwInvalidArguments("websocket expects maxPayloadLength to be an integer", .{}); + return null; + } + server.maxPayloadLength = @intCast(u32, @truncate(i33, @maximum(value.toInt64(), 0))); + } + } + if (object.get(globalObject, "idleTimeout")) |value| { + if (!value.isUndefinedOrNull()) { + if (!value.isAnyInt()) { + globalObject.throwInvalidArguments("websocket expects idleTimeout to be an integer", .{}); + return null; + } + + server.idleTimeout = value.to(u16); + } + } + if (object.get(globalObject, "backpressureLimit")) |value| { + if (!value.isUndefinedOrNull()) { + if (!value.isAnyInt()) { + globalObject.throwInvalidArguments("websocket expects backpressureLimit to be an integer", .{}); + return null; + } + + server.backpressureLimit = @intCast(u32, @truncate(i33, @maximum(value.toInt64(), 0))); + } + } + // if (object.get(globalObject, "sendPings")) |value| { + // if (!value.isUndefinedOrNull()) { + // if (!value.isBoolean()) { + // globalObject.throwInvalidArguments("websocket expects sendPings to be a boolean", .{}); + // return null; + // } + + // server.sendPings = value.toBoolean(); + // } + // } + + if (object.get(globalObject, "closeOnBackpressureLimit")) |value| { + if (!value.isUndefinedOrNull()) { + if (!value.isBoolean()) { + globalObject.throwInvalidArguments("websocket expects closeOnBackpressureLimit to be a boolean", .{}); + return null; + } + + server.closeOnBackpressureLimit = value.toBoolean(); + } + } + + if (object.getTruthy(globalObject, "message")) |message| { + if (!message.isCallable(globalObject.vm())) { + globalObject.throwInvalidArguments("websocket expects a function for the message option", .{}); + return null; + } + server.onMessage = message; + message.ensureStillAlive(); + } + + if (object.getTruthy(globalObject, "open")) |open| { + if (!open.isCallable(globalObject.vm())) { + globalObject.throwInvalidArguments("websocket expects a function for the open option", .{}); + return null; + } + server.onOpen = open; + open.ensureStillAlive(); + } + + if (object.getTruthy(globalObject, "close")) |close| { + if (!close.isCallable(globalObject.vm())) { + globalObject.throwInvalidArguments("websocket expects a function for the close option", .{}); + return null; + } + server.onClose = close; + close.ensureStillAlive(); + } + + if (object.getTruthy(globalObject, "drain")) |drain| { + if (!drain.isCallable(globalObject.vm())) { + globalObject.throwInvalidArguments("websocket expects a function for the drain option", .{}); + return null; + } + server.onDrain = drain; + drain.ensureStillAlive(); + } + + if (object.getTruthy(globalObject, "upgrade")) |upgrade| { + if (!upgrade.isCallable(globalObject.vm())) { + globalObject.throwInvalidArguments("websocket expects a function for the upgrade option", .{}); + return null; + } + server.onUpgrade = upgrade; + upgrade.ensureStillAlive(); + } + + server.protect(); + return server; + } + + pub const List = std.StringArrayHashMapUnmanaged(WebSocketServer); +}; + +const Corker = struct { + args: []const JSValue, + globalObject: *JSC.JSGlobalObject, + callback: JSC.JSValue, + result: JSValue = .zero, + + pub fn run(this: *Corker) void { + this.result = this.callback.call(this.globalObject, this.args); + } +}; + +pub const ServerWebSocket = struct { + handler: *WebSocketServer, + this_value: JSValue = .zero, + websocket: uws.AnyWebSocket = undefined, + closed: bool = false, + + pub usingnamespace JSC.Codegen.JSServerWebSocket; + + const log = Output.scoped(.WebSocketServer, false); + + pub fn onOpen(this: *ServerWebSocket, ws: uws.AnyWebSocket) void { + log("OnOpen", .{}); + + this.websocket = ws; + this.closed = false; + + // the this value is initially set to whatever the user passed in + const value_to_cache = this.this_value; + + var handler = this.handler; + handler.active_connections +|= 1; + var globalObject = handler.globalObject; + + const onOpenHandler = handler.onOpen; + this.this_value = .zero; + if (value_to_cache != .zero) { + const current_this = this.getThisValue(); + ServerWebSocket.dataSetCached(current_this, globalObject, value_to_cache); + } + + if (onOpenHandler.isEmptyOrUndefinedOrNull()) return; + var args = [_]JSValue{this.this_value}; + + var corker = Corker{ + .args = &args, + .globalObject = globalObject, + .callback = onOpenHandler, + }; + ws.cork(&corker, Corker.run); + if (corker.result.isAnyError(globalObject)) { + log("onOpen exception", .{}); + + ws.close(); + _ = ServerWebSocket.dangerouslySetPtr(this.this_value, null); + handler.active_connections -|= 1; + this.this_value.unprotect(); + bun.default_allocator.destroy(this); + globalObject.bunVM().runErrorHandler(corker.result, null); + } + } + + pub fn getThisValue(this: *ServerWebSocket) JSValue { + var this_value = this.this_value; + if (this_value == .zero) { + this_value = this.toJS(this.handler.globalObject); + this_value.protect(); + this.this_value = this_value; + } + return this_value; + } + + pub fn onMessage( + this: *ServerWebSocket, + ws: uws.AnyWebSocket, + message: []const u8, + opcode: uws.Opcode, + ) void { + log("onMessage({d}): {s}", .{ + @enumToInt(opcode), + message, + }); + const onMessageHandler = this.handler.onMessage; + if (onMessageHandler.isEmptyOrUndefinedOrNull()) return; + var globalObject = this.handler.globalObject; + + const arguments = [_]JSValue{ + this.getThisValue(), + switch (opcode) { + .text => brk: { + var str = ZigString.init(message); + str.markUTF8(); + break :brk str.toValueGC(globalObject); + }, + .binary => JSC.ArrayBuffer.create(globalObject, message, .Uint8Array), + else => unreachable, + }, + }; + + var corker = Corker{ + .args = &arguments, + .globalObject = globalObject, + .callback = onMessageHandler, + }; + + ws.cork(&corker, Corker.run); + const result = corker.result; + + if (result.isEmptyOrUndefinedOrNull()) return; + + if (result.isAnyError(globalObject)) { + this.handler.globalObject.bunVM().runErrorHandler(result, null); + return; + } + + if (result.asPromise()) |promise| { + switch (promise.status(globalObject.vm())) { + .Rejected => { + _ = promise.result(globalObject.vm()); + return; + }, + + else => {}, + } + } + } + pub fn onDrain(this: *ServerWebSocket, _: uws.AnyWebSocket) void { + log("onDrain", .{}); + + var handler = this.handler; + if (handler.onDrain != .zero) { + const result = handler.onDrain.call(handler.globalObject, &[_]JSC.JSValue{this.this_value}); + + if (result.isAnyError(handler.globalObject)) { + log("onDrain error", .{}); + handler.globalObject.bunVM().runErrorHandler(result, null); + } + } + } + pub fn onPing(_: *ServerWebSocket, _: uws.AnyWebSocket, _: []const u8) void { + log("onPing", .{}); + } + pub fn onPong(_: *ServerWebSocket, _: uws.AnyWebSocket, _: []const u8) void { + log("onPong", .{}); + } + pub fn onClose(this: *ServerWebSocket, _: uws.AnyWebSocket, code: i32, message: []const u8) void { + log("onClose", .{}); + var handler = this.handler; + this.closed = true; + defer handler.active_connections -|= 1; + + if (handler.onClose != .zero) { + const result = handler.onClose.call( + handler.globalObject, + &[_]JSC.JSValue{ this.this_value, JSValue.jsNumber(code), ZigString.init(message).toValueGC(handler.globalObject) }, + ); + + if (result.isAnyError(handler.globalObject)) { + log("onClose error", .{}); + handler.globalObject.bunVM().runErrorHandler(result, null); + } + } + + this.this_value.unprotect(); + } + + pub fn behavior(comptime ServerType: type, comptime ssl: bool, opts: uws.WebSocketBehavior) uws.WebSocketBehavior { + return uws.WebSocketBehavior.Wrap(ServerType, @This(), ssl).apply(opts); + } + + pub fn constructor(globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) ?*ServerWebSocket { + globalObject.throw("Cannot construct ServerWebSocket", .{}); + return null; + } + + pub fn finalize(this: *ServerWebSocket) callconv(.C) void { + bun.default_allocator.destroy(this); + } + + pub fn publish( + this: *ServerWebSocket, + globalThis: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(.C) JSValue { + const args = callframe.arguments(4); + + if (args.len < 1) { + log("publish()", .{}); + globalThis.throw("publish requires at least 1 argument", .{}); + return .zero; + } + + if (this.closed) { + log("publish() closed", .{}); + return JSValue.jsNumber(0); + } + + const topic_value = args.ptr[0]; + const message_value = args.ptr[1]; + const compress_value = args.ptr[2]; + + if (topic_value.isEmptyOrUndefinedOrNull() or !topic_value.isString()) { + log("publish() topic invalid", .{}); + globalThis.throw("publish requires a topic string", .{}); + return .zero; + } + + var topic_slice = topic_value.toSlice(globalThis, bun.default_allocator); + defer topic_slice.deinit(); + if (topic_slice.len == 0) { + globalThis.throw("publish requires a non-empty topic", .{}); + return JSValue.jsNumber(0); + } + + const compress = args.len > 1 and compress_value.toBoolean(); + + if (message_value.isEmptyOrUndefinedOrNull()) { + globalThis.throw("publish requires a non-empty message", .{}); + return .zero; + } + + if (message_value.asArrayBuffer(globalThis)) |buffer| { + if (buffer.len == 0) { + globalThis.throw("publish requires a non-empty message", .{}); + return .zero; + } + + return JSValue.jsNumber( + // if 0, return 0 + // else return number of bytes sent + @as(i32, @boolToInt(this.websocket.publishWithOptions(topic_slice.slice(), buffer.slice(), .text, compress))) * @intCast(i32, @truncate(u31, buffer.len)), + ); + } + + { + var string_slice = message_value.toSlice(globalThis, bun.default_allocator); + defer string_slice.deinit(); + if (string_slice.len == 0) { + return JSValue.jsNumber(0); + } + + const buffer = string_slice.slice(); + return JSValue.jsNumber( + // if 0, return 0 + // else return number of bytes sent + @as(i32, @boolToInt(this.websocket.publishWithOptions(topic_slice.slice(), buffer, .text, compress))) * @intCast(i32, @truncate(u31, buffer.len)), + ); + } + + return .zero; + } + + pub fn send( + this: *ServerWebSocket, + globalThis: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(.C) JSValue { + const args = callframe.arguments(2); + + if (args.len < 1) { + log("send()", .{}); + globalThis.throw("send requires at least 1 argument", .{}); + return .zero; + } + + if (this.closed) { + log("send() closed", .{}); + return JSValue.jsNumber(0); + } + + const message_value = args.ptr[0]; + const compress_value = args.ptr[1]; + + const compress = args.len > 1 and compress_value.toBoolean(); + + if (message_value.isEmptyOrUndefinedOrNull()) { + globalThis.throw("send requires a non-empty message", .{}); + return .zero; + } + + if (message_value.asArrayBuffer(globalThis)) |buffer| { + if (buffer.len == 0) { + globalThis.throw("send requires a non-empty message", .{}); + return .zero; + } + + switch (this.websocket.send(buffer.slice(), .binary, compress, true)) { + .backpressure => { + log("send() backpressure ({d} bytes)", .{buffer.len}); + return JSValue.jsNumber(-1); + }, + .success => { + log("send() success ({d} bytes)", .{buffer.len}); + return JSValue.jsNumber(buffer.slice().len); + }, + .dropped => { + log("send() dropped ({d} bytes)", .{buffer.len}); + return JSValue.jsNumber(0); + }, + } + } + + { + var string_slice = message_value.toSlice(globalThis, bun.default_allocator); + defer string_slice.deinit(); + if (string_slice.len == 0) { + return JSValue.jsNumber(0); + } + + const buffer = string_slice.slice(); + switch (this.websocket.send(buffer, .text, compress, true)) { + .backpressure => { + log("send() backpressure ({d} bytes string)", .{buffer.len}); + return JSValue.jsNumber(-1); + }, + .success => { + log("send() success ({d} bytes string)", .{buffer.len}); + return JSValue.jsNumber(buffer.len); + }, + .dropped => { + log("send() dropped ({d} bytes string)", .{buffer.len}); + return JSValue.jsNumber(0); + }, + } + } + + return .zero; + } + + pub fn getData( + _: *ServerWebSocket, + _: *JSC.JSGlobalObject, + ) callconv(.C) JSValue { + log("getData()", .{}); + return JSValue.jsUndefined(); + } + + pub fn setData( + this: *ServerWebSocket, + globalObject: *JSC.JSGlobalObject, + value: JSC.JSValue, + ) callconv(.C) bool { + log("setData()", .{}); + ServerWebSocket.dataSetCached(this.this_value, globalObject, value); + return true; + } + + pub fn getReadyState( + this: *ServerWebSocket, + _: *JSC.JSGlobalObject, + ) callconv(.C) JSValue { + log("getReadyState()", .{}); + + if (this.closed) { + return JSValue.jsNumber(3); + } + + return JSValue.jsNumber(1); + } + + pub fn close( + this: *ServerWebSocket, + globalThis: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(.C) JSValue { + const args = callframe.arguments(2); + log("close()", .{}); + + if (this.closed) { + return .zero; + } + + const code = if (args.len > 0) args.ptr[0].toInt32() else @as(i32, 1000); + var message_value = if (args.len > 1) args.ptr[1].toSlice(globalThis, bun.default_allocator) else ZigString.Slice.empty; + defer message_value.deinit(); + if (code > 0) { + this.websocket.end(code, message_value.slice()); + } else { + this.closed = true; + this.this_value.unprotect(); + this.websocket.close(); + } + + return JSValue.jsUndefined(); + } + pub fn getBufferedAmount( + this: *ServerWebSocket, + _: *JSC.JSGlobalObject, + _: *JSC.CallFrame, + ) callconv(.C) JSValue { + log("getBufferedAmount()", .{}); + + if (this.closed) { + return JSValue.jsNumber(0); + } + + return JSValue.jsNumber(this.websocket.getBufferedAmount()); + } + pub fn subscribe( + this: *ServerWebSocket, + globalThis: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(.C) JSValue { + const args = callframe.arguments(1); + if (args.len < 1) { + globalThis.throw("subscribe requires at least 1 argument", .{}); + return .zero; + } + + if (this.closed) { + return JSValue.jsBoolean(true); + } + + var topic = args.ptr[0].toSlice(globalThis, bun.default_allocator); + defer topic.deinit(); + + if (topic.len == 0) { + globalThis.throw("subscribe requires a non-empty topic name", .{}); + return .zero; + } + + return JSValue.jsBoolean(this.websocket.subscribe(topic.slice())); + } + pub fn unsubscribe( + this: *ServerWebSocket, + globalThis: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(.C) JSValue { + const args = callframe.arguments(1); + if (args.len < 1) { + globalThis.throw("unsubscribe requires at least 1 argument", .{}); + return .zero; + } + + if (this.closed) { + return JSValue.jsBoolean(true); + } + + var topic = args.ptr[0].toSlice(globalThis, bun.default_allocator); + defer topic.deinit(); + + if (topic.len == 0) { + globalThis.throw("unsubscribe requires a non-empty topic name", .{}); + return .zero; + } + + return JSValue.jsBoolean(this.websocket.unsubscribe(topic.slice())); + } + pub fn isSubscribed( + this: *ServerWebSocket, + globalThis: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(.C) JSValue { + const args = callframe.arguments(1); + if (args.len < 1) { + globalThis.throw("isSubscribed requires at least 1 argument", .{}); + return .zero; + } + + if (this.closed) { + return JSValue.jsBoolean(false); + } + + var topic = args.ptr[0].toSlice(globalThis, bun.default_allocator); + defer topic.deinit(); + + if (topic.len == 0) { + globalThis.throw("isSubscribed requires a non-empty topic name", .{}); + return .zero; + } + + return JSValue.jsBoolean(this.websocket.isSubscribed(topic.slice())); + } + + // pub fn getTopics( + // this: *ServerWebSocket, + // globalThis: *JSC.JSGlobalObject, + // ) callconv(.C) JSValue { + // if (this.closed) { + // return JSValue.createStringArray(globalThis, bun.default_allocator, null, 0, false); + // } + + // this + // } + + pub fn getRemoteAddress( + this: *ServerWebSocket, + globalThis: *JSC.JSGlobalObject, + ) callconv(.C) JSValue { + if (this.closed) { + return JSValue.jsUndefined(); + } + + var buf: [512]u8 = undefined; + const address = this.websocket.getRemoteAddress(&buf); + if (address.len == 0) { + return JSValue.jsUndefined(); + } + + return ZigString.init(address).toValueGC(globalThis); + } +}; + pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { return struct { pub const ssl_enabled = ssl_enabled_; @@ -2119,6 +3043,9 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { .pendingRequests = .{ .get = JSC.getterWrap(ThisServer, "getPendingRequests"), }, + .pendingSockets = .{ + .get = JSC.getterWrap(ThisServer, "getPendingSockets"), + }, }, ); @@ -2150,6 +3077,54 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { this.config.onError = new_config.onError; } + if (new_config.websocket.onMessage != .zero or new_config.websocket.onOpen != .zero) { + this.config.websocket.unprotect(); + if (this.config.websocket.onMessage == .zero) { + this.app.ws("/*", this, 0, ServerWebSocket.behavior( + ThisServer, + ssl_enabled, + this.config.websocket.toBehavior(), + )); + } + + new_config.websocket.globalObject = ctx; + this.config.websocket = new_config.websocket; + } else if (this.config.websocket.onMessage != .zero or this.config.websocket.onOpen != .zero) { + this.config.websocket.unprotect(); + this.config.websocket = .{}; + } + + // we are going to leak the old memory + const new_keys = new_config.websockets.keys(); + const old_keys = this.config.websockets.keys(); + + if (new_keys.len + old_keys.len > 0) { + var all_match = old_keys.len <= new_keys.len; + + // any existing websockets will now call the new config + for (new_keys) |key, i| { + if (this.config.websockets.getPtr(key)) |old_val| { + old_val.unprotect(); + old_val.* = new_config.websockets.values()[i]; + old_val.globalObject = ctx; + } else { + all_match = false; + var new_value = &new_config.websockets.values()[i]; + new_value.globalObject = ctx; + this.app.ws( + key, + this, + i, + ServerWebSocket.behavior(ThisServer, ssl_enabled, new_value.toBehavior()), + ); + } + } + + if (!all_match) { + this.config.websockets = new_config.websockets; + } + } + return this.thisObject.asObjectRef(); } @@ -2289,6 +3264,10 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { return JSC.JSValue.jsNumber(@intCast(i32, @truncate(u31, this.pending_requests))); } + pub fn getPendingSockets(this: *ThisServer) JSC.JSValue { + return JSC.JSValue.jsNumber(@intCast(i32, @truncate(u31, this.activeSocketsCount()))); + } + pub fn getHostname(this: *ThisServer, globalThis: *JSGlobalObject) JSC.JSValue { return ZigString.init(bun.span(this.config.hostname)).toValue(globalThis); } @@ -2317,8 +3296,21 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { this.deinitIfWeCan(); } + pub fn activeSocketsCount(this: *const ThisServer) u32 { + var count = this.config.websocket.active_connections; + for (this.config.websockets.values()) |conn| { + count += conn.active_connections; + } + + return @truncate(u32, count); + } + + pub fn hasActiveWebSockets(this: *const ThisServer) bool { + return this.activeSocketsCount() > 0; + } + pub fn deinitIfWeCan(this: *ThisServer) void { - if (this.pending_requests == 0 and this.listener == null and this.has_js_deinited) { + if (this.pending_requests == 0 and this.listener == null and this.has_js_deinited and !this.hasActiveWebSockets()) { this.unref(); this.deinit(); } @@ -2517,10 +3509,13 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { } } - pub fn onRequest(this: *ThisServer, req: *uws.Request, resp: *App.Response) void { + pub fn onRequest( + this: *ThisServer, + req: *uws.Request, + resp: *App.Response, + ) void { JSC.markBinding(); this.pending_requests += 1; - var vm = this.vm; req.setYield(false); var ctx = this.request_pool_allocator.create(RequestContext) catch @panic("ran out of memory"); ctx.create(this, req, resp); @@ -2573,112 +3568,21 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { } // We keep the Request object alive for the duration of the request so that we can remove the pointer to the UWS request object. - var args = [_]JSC.C.JSValueRef{request_object.toJS(this.globalThis).asObjectRef()}; + var args = [_]JSC.C.JSValueRef{ + request_object.toJS(this.globalThis).asObjectRef(), + }; ctx.request_js_object = args[0]; const request_value = JSValue.c(args[0]); request_value.ensureStillAlive(); const response_value = JSC.C.JSObjectCallAsFunctionReturnValue(this.globalThis, this.config.onRequest.asObjectRef(), this.thisObject.asObjectRef(), 1, &args); - request_value.ensureStillAlive(); - response_value.ensureStillAlive(); - if (ctx.aborted) { - ctx.finalizeForAbort(); - return; - } - if (response_value.isEmptyOrUndefinedOrNull() and !ctx.resp.hasResponded()) { - ctx.renderMissing(); - return; - } - - if (response_value.isError() or response_value.isAggregateError(this.globalThis) or response_value.isException(this.globalThis.vm())) { - ctx.runErrorHandler(response_value); - - return; - } - - if (response_value.as(JSC.WebCore.Response)) |response| { - ctx.response_jsvalue = response_value; - ctx.response_jsvalue.ensureStillAlive(); - ctx.response_protected = false; - switch (response.body.value) { - .Blob => |*blob| { - if (blob.needsToReadFile()) { - response_value.protect(); - ctx.response_protected = true; - } - }, - .Locked => { - response_value.protect(); - ctx.response_protected = true; - }, - else => {}, - } - ctx.render(response); - return; - } - - var wait_for_promise = false; - - if (response_value.asPromise()) |promise| { - // If we immediately have the value available, we can skip the extra event loop tick - switch (promise.status(vm.global.vm())) { - .Pending => {}, - .Fulfilled => { - ctx.handleResolve(promise.result(vm.global.vm())); - return; - }, - .Rejected => { - ctx.handleReject(promise.result(vm.global.vm())); - return; - }, - } - wait_for_promise = true; - // I don't think this case should happen - // But I'm uncertain - } else if (response_value.asInternalPromise()) |promise| { - switch (promise.status(vm.global.vm())) { - .Pending => {}, - .Fulfilled => { - ctx.handleResolve(promise.result(vm.global.vm())); - return; - }, - .Rejected => { - ctx.handleReject(promise.result(vm.global.vm())); - return; - }, - } - wait_for_promise = true; - } - - if (wait_for_promise) { - request_object.uws_request = req; - - request_object.ensureURL() catch { - request_object.url = ""; - }; - - // we have to clone the request headers here since they will soon belong to a different request - if (request_object.headers == null) { - request_object.headers = JSC.FetchHeaders.createFromUWS(this.globalThis, req); - } - - if (comptime debug_mode) { - ctx.pathname = bun.default_allocator.dupe(u8, request_object.url) catch unreachable; - } - - // This object dies after the stack frame is popped - // so we have to clear it in here too - request_object.uws_request = null; - - ctx.setAbortHandler(); - ctx.pending_promises_for_abort += 1; - - response_value.then(this.globalThis, ctx, RequestContext.onResolve, RequestContext.onReject); - return; - } - - // The user returned something that wasn't a promise or a promise with a response - if (!ctx.resp.hasResponded()) ctx.renderMissing(); + ctx.onResponse( + this, + req, + request_object, + request_value, + response_value, + ); } pub fn listen(this: *ThisServer) void { @@ -2701,6 +3605,30 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { this.app = App.create(.{}); } + const websocket_patterns = this.config.websockets.keys(); + { + const values = this.config.websockets.values(); + for (websocket_patterns) |pattern, i| { + values[i].globalObject = this.globalThis; + + this.app.ws(pattern, this, i + 1, ServerWebSocket.behavior( + ThisServer, + ssl_enabled, + values[i].toBehavior(), + )); + } else { + if (this.config.websocket.onMessage != .zero or this.config.websocket.onOpen != .zero) { + this.config.websocket.globalObject = this.globalThis; + this.app.ws( + "/*", + this, + 0, + ServerWebSocket.behavior(ThisServer, ssl_enabled, this.config.websocket.toBehavior()), + ); + } + } + } + this.app.any("/*", *ThisServer, this, onRequest); if (comptime debug_mode) { @@ -2718,12 +3646,137 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { this.config.hostname; this.ref(); + this.app.listenWithConfig(*ThisServer, this, onListen, .{ .port = this.config.port, .host = host, .options = 0, }); } + + pub fn onWebSocketUpgrade(this: *ThisServer, resp: *App.Response, req: *uws.Request, ctx: *uws.uws_socket_context_t, id: usize) void { + var websocket_handler: *WebSocketServer = switch (id) { + 0 => &this.config.websocket, + else => &this.config.websockets.values()[id - 1], + }; + req.setYield(false); + + const onUpgrade = websocket_handler.onUpgrade; + + if (onUpgrade == .zero) { + var upgrader = this.allocator.create(ServerWebSocket) catch @panic("Out of memory"); + upgrader.* = .{ + .handler = websocket_handler, + .this_value = .zero, + }; + resp.upgrade( + *ServerWebSocket, + upgrader, + req.header("sec-websocket-key") orelse "", + req.header("sec-websocket-protocol") orelse "", + req.header("sec-websocket-extensions") orelse "", + ctx, + ); + return; + } + + const method = HTTP.Method.which(req.method()) orelse HTTP.Method.GET; + + var request_object = this.allocator.create(JSC.WebCore.Request) catch unreachable; + request_object.* = .{ + .url = "", + .method = method, + .uws_request = req, + .base_url_string_for_joining = this.base_url_string_for_joining, + .body = .{ + .Empty = .{}, + }, + }; + + var args = [_]JSC.JSValue{ + request_object.toJS(this.globalThis), + }; + var request_value = args[0]; + request_value.ensureStillAlive(); + const response_value = websocket_handler.onUpgrade.call(this.globalThis, &args); + request_value.ensureStillAlive(); + response_value.ensureStillAlive(); + + if (response_value.isBoolean() or response_value.isEmptyOrUndefinedOrNull()) { + if (response_value.toBoolean()) { + var upgrader = this.allocator.create(ServerWebSocket) catch @panic("Out of memory"); + upgrader.* = .{ + .handler = websocket_handler, + }; + resp.upgrade( + *ServerWebSocket, + upgrader, + req.header("sec-websocket-key") orelse "", + req.header("sec-websocket-protocol") orelse "", + req.header("sec-websocket-extensions") orelse "", + ctx, + ); + return; + } else { + req.setYield(true); + return; + } + } + + if (response_value.as(Response)) |response| { + if (response.statusCode() == 101) { + var upgrader = this.allocator.create(ServerWebSocket) catch @panic("Out of memory"); + upgrader.* = .{ + .handler = websocket_handler, + .this_value = response_value, + }; + response_value.ensureStillAlive(); + resp.upgrade( + *ServerWebSocket, + upgrader, + response.header(.SecWebSocketKey) orelse req.header("sec-websocket-key") orelse "", + response.header(.SecWebSocketProtocol) orelse req.header("sec-websocket-protocol") orelse "", + response.header(.SecWebSocketExtensions) orelse req.header("sec-websocket-extensions") orelse "", + ctx, + ); + return; + } + } + + // The returned object becomes the data for the ServerWebSocket + if (response_value.isObject() or (response_value.isString() and response_value.getLengthOfArray(this.globalThis) > 0)) { + var upgrader = this.allocator.create(ServerWebSocket) catch @panic("Out of memory"); + upgrader.* = .{ + .handler = websocket_handler, + .this_value = response_value, + }; + response_value.ensureStillAlive(); + + resp.upgrade( + *ServerWebSocket, + upgrader, + req.header("sec-websocket-key") orelse "", + req.header("sec-websocket-protocol") orelse "", + req.header("sec-websocket-extensions") orelse "", + ctx, + ); + return; + } + + var req_ctx = this.request_pool_allocator.create(RequestContext) catch @panic("ran out of memory"); + req_ctx.create(this, req, resp); + req_ctx.request_js_object = request_value.asObjectRef(); + req_ctx.response_jsvalue = response_value; + req_ctx.resp = resp; + + req_ctx.onResponse( + this, + req, + request_object, + request_value, + response_value, + ); + } }; } diff --git a/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h b/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h index cd67081d1..5014f0d4e 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h @@ -7,7 +7,8 @@ std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForSHA224Constructor;std: std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForSHA512Constructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForSHA384; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForSHA384Constructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForSHA256; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForSHA256Constructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForSHA512_256; -std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForSHA512_256Constructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTextDecoder; +std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForSHA512_256Constructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForServerWebSocket; +std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForServerWebSocketConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTextDecoder; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTextDecoderConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForRequest; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForRequestConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForResponse; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForResponseConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForBlob; diff --git a/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h b/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h index 4d1104f99..149dfe8b8 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h +++ b/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h @@ -7,7 +7,8 @@ std::unique_ptr<IsoSubspace> m_subspaceForSHA224Constructor;std::unique_ptr<IsoS std::unique_ptr<IsoSubspace> m_subspaceForSHA512Constructor;std::unique_ptr<IsoSubspace> m_subspaceForSHA384; std::unique_ptr<IsoSubspace> m_subspaceForSHA384Constructor;std::unique_ptr<IsoSubspace> m_subspaceForSHA256; std::unique_ptr<IsoSubspace> m_subspaceForSHA256Constructor;std::unique_ptr<IsoSubspace> m_subspaceForSHA512_256; -std::unique_ptr<IsoSubspace> m_subspaceForSHA512_256Constructor;std::unique_ptr<IsoSubspace> m_subspaceForTextDecoder; +std::unique_ptr<IsoSubspace> m_subspaceForSHA512_256Constructor;std::unique_ptr<IsoSubspace> m_subspaceForServerWebSocket; +std::unique_ptr<IsoSubspace> m_subspaceForServerWebSocketConstructor;std::unique_ptr<IsoSubspace> m_subspaceForTextDecoder; std::unique_ptr<IsoSubspace> m_subspaceForTextDecoderConstructor;std::unique_ptr<IsoSubspace> m_subspaceForRequest; std::unique_ptr<IsoSubspace> m_subspaceForRequestConstructor;std::unique_ptr<IsoSubspace> m_subspaceForResponse; std::unique_ptr<IsoSubspace> m_subspaceForResponseConstructor;std::unique_ptr<IsoSubspace> m_subspaceForBlob; diff --git a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h index c37518fe5..3de6262da 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h +++ b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h @@ -52,6 +52,12 @@ JSC::Structure* JSSHA512_256Structure() { return m_JSSHA512_256.getInitializedOn JSC::LazyClassStructure m_JSSHA512_256; bool hasJSSHA512_256SetterValue { false }; mutable JSC::WriteBarrier<JSC::Unknown> m_JSSHA512_256SetterValue; +JSC::Structure* JSServerWebSocketStructure() { return m_JSServerWebSocket.getInitializedOnMainThread(this); } + JSC::JSObject* JSServerWebSocketConstructor() { return m_JSServerWebSocket.constructorInitializedOnMainThread(this); } + JSC::JSValue JSServerWebSocketPrototype() { return m_JSServerWebSocket.prototypeInitializedOnMainThread(this); } + JSC::LazyClassStructure m_JSServerWebSocket; + bool hasJSServerWebSocketSetterValue { false }; + mutable JSC::WriteBarrier<JSC::Unknown> m_JSServerWebSocketSetterValue; JSC::Structure* JSTextDecoderStructure() { return m_JSTextDecoder.getInitializedOnMainThread(this); } JSC::JSObject* JSTextDecoderConstructor() { return m_JSTextDecoder.constructorInitializedOnMainThread(this); } JSC::JSValue JSTextDecoderPrototype() { return m_JSTextDecoder.prototypeInitializedOnMainThread(this); } diff --git a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h index 1b6ac7ade..e449c2904 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h +++ b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h @@ -53,6 +53,12 @@ void GlobalObject::initGeneratedLazyClasses() { init.setStructure(WebCore::JSSHA512_256::createStructure(init.vm, init.global, init.prototype)); init.setConstructor(WebCore::JSSHA512_256Constructor::create(init.vm, init.global, WebCore::JSSHA512_256Constructor::createStructure(init.vm, init.global, init.global->functionPrototype()), jsCast<WebCore::JSSHA512_256Prototype*>(init.prototype))); }); + m_JSServerWebSocket.initLater( + [](LazyClassStructure::Initializer& init) { + init.setPrototype(WebCore::JSServerWebSocket::createPrototype(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.global))); + init.setStructure(WebCore::JSServerWebSocket::createStructure(init.vm, init.global, init.prototype)); + init.setConstructor(WebCore::JSServerWebSocketConstructor::create(init.vm, init.global, WebCore::JSServerWebSocketConstructor::createStructure(init.vm, init.global, init.global->functionPrototype()), jsCast<WebCore::JSServerWebSocketPrototype*>(init.prototype))); + }); m_JSTextDecoder.initLater( [](LazyClassStructure::Initializer& init) { init.setPrototype(WebCore::JSTextDecoder::createPrototype(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.global))); @@ -90,6 +96,7 @@ void GlobalObject::visitGeneratedLazyClasses(GlobalObject *thisObject, Visitor& thisObject->m_JSSHA384.visit(visitor); visitor.append(thisObject->m_JSSHA384SetterValue); thisObject->m_JSSHA256.visit(visitor); visitor.append(thisObject->m_JSSHA256SetterValue); thisObject->m_JSSHA512_256.visit(visitor); visitor.append(thisObject->m_JSSHA512_256SetterValue); + thisObject->m_JSServerWebSocket.visit(visitor); visitor.append(thisObject->m_JSServerWebSocketSetterValue); thisObject->m_JSTextDecoder.visit(visitor); visitor.append(thisObject->m_JSTextDecoderSetterValue); thisObject->m_JSRequest.visit(visitor); visitor.append(thisObject->m_JSRequestSetterValue); thisObject->m_JSResponse.visit(visitor); visitor.append(thisObject->m_JSResponseSetterValue); diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp index c737103fc..9fbc436ac 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.cpp +++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp @@ -199,6 +199,12 @@ JSC_DEFINE_CUSTOM_GETTER(SubprocessPrototype__readableGetterWrap, (JSGlobalObjec thisObject->m_stdout.set(vm, thisObject, result); RELEASE_AND_RETURN(throwScope, JSValue::encode(result)); } +extern "C" void SubprocessPrototype__readableSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject *globalObject, JSC::EncodedJSValue value) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast<JSSubprocess*>(JSValue::decode(thisValue)); + thisObject->m_stdout.set(vm, thisObject, JSValue::decode(value)); +} JSC_DEFINE_HOST_FUNCTION(SubprocessPrototype__refCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) @@ -236,6 +242,12 @@ JSC_DEFINE_CUSTOM_GETTER(SubprocessPrototype__stderrGetterWrap, (JSGlobalObject thisObject->m_stderr.set(vm, thisObject, result); RELEASE_AND_RETURN(throwScope, JSValue::encode(result)); } +extern "C" void SubprocessPrototype__stderrSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject *globalObject, JSC::EncodedJSValue value) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast<JSSubprocess*>(JSValue::decode(thisValue)); + thisObject->m_stderr.set(vm, thisObject, JSValue::decode(value)); +} JSC_DEFINE_CUSTOM_GETTER(SubprocessPrototype__stdinGetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) @@ -256,6 +268,12 @@ JSC_DEFINE_CUSTOM_GETTER(SubprocessPrototype__stdinGetterWrap, (JSGlobalObject * thisObject->m_stdin.set(vm, thisObject, result); RELEASE_AND_RETURN(throwScope, JSValue::encode(result)); } +extern "C" void SubprocessPrototype__stdinSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject *globalObject, JSC::EncodedJSValue value) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast<JSSubprocess*>(JSValue::decode(thisValue)); + thisObject->m_stdin.set(vm, thisObject, JSValue::decode(value)); +} JSC_DEFINE_CUSTOM_GETTER(SubprocessPrototype__stdoutGetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) @@ -276,6 +294,12 @@ JSC_DEFINE_CUSTOM_GETTER(SubprocessPrototype__stdoutGetterWrap, (JSGlobalObject thisObject->m_stdout.set(vm, thisObject, result); RELEASE_AND_RETURN(throwScope, JSValue::encode(result)); } +extern "C" void SubprocessPrototype__stdoutSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject *globalObject, JSC::EncodedJSValue value) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast<JSSubprocess*>(JSValue::decode(thisValue)); + thisObject->m_stdout.set(vm, thisObject, JSValue::decode(value)); +} JSC_DEFINE_HOST_FUNCTION(SubprocessPrototype__unrefCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) @@ -313,6 +337,12 @@ JSC_DEFINE_CUSTOM_GETTER(SubprocessPrototype__writableGetterWrap, (JSGlobalObjec thisObject->m_stdin.set(vm, thisObject, result); RELEASE_AND_RETURN(throwScope, JSValue::encode(result)); } +extern "C" void SubprocessPrototype__writableSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject *globalObject, JSC::EncodedJSValue value) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast<JSSubprocess*>(JSValue::decode(thisValue)); + thisObject->m_stdin.set(vm, thisObject, JSValue::decode(value)); +} void JSSubprocessPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) @@ -2327,7 +2357,417 @@ void JSSHA512_256::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) JSObject* JSSHA512_256::createPrototype(VM& vm, JSDOMGlobalObject* globalObject) { return JSSHA512_256Prototype::create(vm, globalObject, JSSHA512_256Prototype::createStructure(vm, globalObject, globalObject->objectPrototype())); -}extern "C" void* TextDecoderClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); +}extern "C" void* ServerWebSocketClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); +JSC_DECLARE_CUSTOM_GETTER(jsServerWebSocketConstructor); +extern "C" void ServerWebSocketClass__finalize(void*); + +extern "C" EncodedJSValue ServerWebSocketPrototype__close(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ServerWebSocketPrototype__closeCallback); + + +extern "C" JSC::EncodedJSValue ServerWebSocketPrototype__getData(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); +JSC_DECLARE_CUSTOM_GETTER(ServerWebSocketPrototype__dataGetterWrap); + + +extern "C" EncodedJSValue ServerWebSocketPrototype__getBufferedAmount(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ServerWebSocketPrototype__getBufferedAmountCallback); + + +extern "C" EncodedJSValue ServerWebSocketPrototype__isSubscribed(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ServerWebSocketPrototype__isSubscribedCallback); + + +extern "C" EncodedJSValue ServerWebSocketPrototype__publish(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ServerWebSocketPrototype__publishCallback); + + +extern "C" JSC::EncodedJSValue ServerWebSocketPrototype__getReadyState(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); +JSC_DECLARE_CUSTOM_GETTER(ServerWebSocketPrototype__readyStateGetterWrap); + + +extern "C" JSC::EncodedJSValue ServerWebSocketPrototype__getRemoteAddress(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); +JSC_DECLARE_CUSTOM_GETTER(ServerWebSocketPrototype__remoteAddressGetterWrap); + + +extern "C" EncodedJSValue ServerWebSocketPrototype__send(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ServerWebSocketPrototype__sendCallback); + + +extern "C" EncodedJSValue ServerWebSocketPrototype__subscribe(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ServerWebSocketPrototype__subscribeCallback); + + +extern "C" EncodedJSValue ServerWebSocketPrototype__unsubscribe(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ServerWebSocketPrototype__unsubscribeCallback); + + +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSServerWebSocketPrototype, JSServerWebSocketPrototype::Base); + + + static const HashTableValue JSServerWebSocketPrototypeTableValues[] = { +{ "close"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ServerWebSocketPrototype__closeCallback, 1 } } , +{ "data"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { HashTableValue::GetterSetterType, ServerWebSocketPrototype__dataGetterWrap, 0 } } , +{ "getBufferedAmount"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ServerWebSocketPrototype__getBufferedAmountCallback, 0 } } , +{ "isSubscribed"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ServerWebSocketPrototype__isSubscribedCallback, 1 } } , +{ "publish"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ServerWebSocketPrototype__publishCallback, 3 } } , +{ "readyState"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { HashTableValue::GetterSetterType, ServerWebSocketPrototype__readyStateGetterWrap, 0 } } , +{ "remoteAddress"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { HashTableValue::GetterSetterType, ServerWebSocketPrototype__remoteAddressGetterWrap, 0 } } , +{ "send"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ServerWebSocketPrototype__sendCallback, 2 } } , +{ "subscribe"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ServerWebSocketPrototype__subscribeCallback, 1 } } , +{ "unsubscribe"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ServerWebSocketPrototype__unsubscribeCallback, 1 } } + }; + + +const ClassInfo JSServerWebSocketPrototype::s_info = { "ServerWebSocket"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSServerWebSocketPrototype) }; + + + +JSC_DEFINE_CUSTOM_GETTER(jsServerWebSocketConstructor, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject); + auto* prototype = jsDynamicCast<JSServerWebSocketPrototype*>(JSValue::decode(thisValue)); + + if (UNLIKELY(!prototype)) + return throwVMTypeError(lexicalGlobalObject, throwScope); + return JSValue::encode(globalObject->JSServerWebSocketConstructor()); +} + + + +JSC_DEFINE_HOST_FUNCTION(ServerWebSocketPrototype__closeCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSServerWebSocket* thisObject = jsDynamicCast<JSServerWebSocket*>(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + return ServerWebSocketPrototype__close(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + + +JSC_DEFINE_CUSTOM_GETTER(ServerWebSocketPrototype__dataGetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + auto& vm = lexicalGlobalObject->vm(); + Zig::GlobalObject *globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + JSServerWebSocket* thisObject = jsCast<JSServerWebSocket*>(JSValue::decode(thisValue)); + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + if (JSValue cachedValue = thisObject->m_data.get()) + return JSValue::encode(cachedValue); + + JSC::JSValue result = JSC::JSValue::decode( + ServerWebSocketPrototype__getData(thisObject->wrapped(), globalObject) + ); + RETURN_IF_EXCEPTION(throwScope, {}); + thisObject->m_data.set(vm, thisObject, result); + RELEASE_AND_RETURN(throwScope, JSValue::encode(result)); +} +extern "C" void ServerWebSocketPrototype__dataSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject *globalObject, JSC::EncodedJSValue value) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast<JSServerWebSocket*>(JSValue::decode(thisValue)); + thisObject->m_data.set(vm, thisObject, JSValue::decode(value)); +} + + +JSC_DEFINE_HOST_FUNCTION(ServerWebSocketPrototype__getBufferedAmountCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSServerWebSocket* thisObject = jsDynamicCast<JSServerWebSocket*>(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + return ServerWebSocketPrototype__getBufferedAmount(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + + +JSC_DEFINE_HOST_FUNCTION(ServerWebSocketPrototype__isSubscribedCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSServerWebSocket* thisObject = jsDynamicCast<JSServerWebSocket*>(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + return ServerWebSocketPrototype__isSubscribed(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + + +JSC_DEFINE_HOST_FUNCTION(ServerWebSocketPrototype__publishCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSServerWebSocket* thisObject = jsDynamicCast<JSServerWebSocket*>(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + return ServerWebSocketPrototype__publish(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + + +JSC_DEFINE_CUSTOM_GETTER(ServerWebSocketPrototype__readyStateGetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + auto& vm = lexicalGlobalObject->vm(); + Zig::GlobalObject *globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + JSServerWebSocket* thisObject = jsCast<JSServerWebSocket*>(JSValue::decode(thisValue)); + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + JSC::EncodedJSValue result = ServerWebSocketPrototype__getReadyState(thisObject->wrapped(), globalObject); + RETURN_IF_EXCEPTION(throwScope, {}); + RELEASE_AND_RETURN(throwScope, result); +} + + +JSC_DEFINE_CUSTOM_GETTER(ServerWebSocketPrototype__remoteAddressGetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + auto& vm = lexicalGlobalObject->vm(); + Zig::GlobalObject *globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + JSServerWebSocket* thisObject = jsCast<JSServerWebSocket*>(JSValue::decode(thisValue)); + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + if (JSValue cachedValue = thisObject->m_remoteAddress.get()) + return JSValue::encode(cachedValue); + + JSC::JSValue result = JSC::JSValue::decode( + ServerWebSocketPrototype__getRemoteAddress(thisObject->wrapped(), globalObject) + ); + RETURN_IF_EXCEPTION(throwScope, {}); + thisObject->m_remoteAddress.set(vm, thisObject, result); + RELEASE_AND_RETURN(throwScope, JSValue::encode(result)); +} +extern "C" void ServerWebSocketPrototype__remoteAddressSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject *globalObject, JSC::EncodedJSValue value) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast<JSServerWebSocket*>(JSValue::decode(thisValue)); + thisObject->m_remoteAddress.set(vm, thisObject, JSValue::decode(value)); +} + + +JSC_DEFINE_HOST_FUNCTION(ServerWebSocketPrototype__sendCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSServerWebSocket* thisObject = jsDynamicCast<JSServerWebSocket*>(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + return ServerWebSocketPrototype__send(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + + +JSC_DEFINE_HOST_FUNCTION(ServerWebSocketPrototype__subscribeCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSServerWebSocket* thisObject = jsDynamicCast<JSServerWebSocket*>(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + return ServerWebSocketPrototype__subscribe(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + + +JSC_DEFINE_HOST_FUNCTION(ServerWebSocketPrototype__unsubscribeCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSServerWebSocket* thisObject = jsDynamicCast<JSServerWebSocket*>(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + return ServerWebSocketPrototype__unsubscribe(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + + +void JSServerWebSocketPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSServerWebSocket::info(), JSServerWebSocketPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +void JSServerWebSocketConstructor::finishCreation(VM& vm, JSC::JSGlobalObject* globalObject, JSServerWebSocketPrototype* prototype) +{ + Base::finishCreation(vm, 0, "ServerWebSocket"_s, PropertyAdditionMode::WithoutStructureTransition); + + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + ASSERT(inherits(info())); +} + +JSServerWebSocketConstructor* JSServerWebSocketConstructor::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSServerWebSocketPrototype* prototype) { + JSServerWebSocketConstructor* ptr = new (NotNull, JSC::allocateCell<JSServerWebSocketConstructor>(vm)) JSServerWebSocketConstructor(vm, structure, construct); + ptr->finishCreation(vm, globalObject, prototype); + return ptr; +} + +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSServerWebSocketConstructor::construct(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) +{ + Zig::GlobalObject *globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject); + JSC::VM &vm = globalObject->vm(); + JSObject* newTarget = asObject(callFrame->newTarget()); + auto* constructor = globalObject->JSServerWebSocketConstructor(); + Structure* structure = globalObject->JSServerWebSocketStructure(); + if (constructor != newTarget) { + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* functionGlobalObject = reinterpret_cast<Zig::GlobalObject*>( + // ShadowRealm functions belong to a different global object. + getFunctionRealm(globalObject, newTarget) + ); + RETURN_IF_EXCEPTION(scope, {}); + structure = InternalFunction::createSubclassStructure( + globalObject, + newTarget, + functionGlobalObject->JSServerWebSocketStructure() + ); + } + + void* ptr = ServerWebSocketClass__construct(globalObject, callFrame); + + if (UNLIKELY(!ptr)) { + return JSValue::encode(JSC::jsUndefined()); + } + + JSServerWebSocket* instance = JSServerWebSocket::create(vm, globalObject, structure, ptr); + + + return JSValue::encode(instance); +} + +extern "C" EncodedJSValue ServerWebSocket__create(Zig::GlobalObject* globalObject, void* ptr) { + auto &vm = globalObject->vm(); + JSC::Structure* structure = globalObject->JSServerWebSocketStructure(); + JSServerWebSocket* instance = JSServerWebSocket::create(vm, globalObject, structure, ptr); + + return JSValue::encode(instance); +} + +void JSServerWebSocketConstructor::initializeProperties(VM& vm, JSC::JSGlobalObject* globalObject, JSServerWebSocketPrototype* prototype) +{ + +} + +const ClassInfo JSServerWebSocketConstructor::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSServerWebSocketConstructor) }; + + + extern "C" EncodedJSValue ServerWebSocket__getConstructor(Zig::GlobalObject* globalObject) { + return JSValue::encode(globalObject->JSServerWebSocketConstructor()); + } + +JSServerWebSocket::~JSServerWebSocket() +{ + if (m_ctx) { + ServerWebSocketClass__finalize(m_ctx); + } +} +void JSServerWebSocket::destroy(JSCell* cell) +{ + static_cast<JSServerWebSocket*>(cell)->JSServerWebSocket::~JSServerWebSocket(); +} + +const ClassInfo JSServerWebSocket::s_info = { "ServerWebSocket"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSServerWebSocket) }; + +void JSServerWebSocket::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); +} + +JSServerWebSocket* JSServerWebSocket::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx) { + JSServerWebSocket* ptr = new (NotNull, JSC::allocateCell<JSServerWebSocket>(vm)) JSServerWebSocket(vm, structure, ctx); + ptr->finishCreation(vm); + return ptr; +} + +extern "C" void* ServerWebSocket__fromJS(JSC::EncodedJSValue value) { + JSServerWebSocket* object = JSC::jsDynamicCast<JSServerWebSocket*>(JSValue::decode(value)); + if (!object) + return nullptr; + + return object->wrapped(); +} + +extern "C" bool ServerWebSocket__dangerouslySetPtr(JSC::EncodedJSValue value, void* ptr) { + JSServerWebSocket* object = JSC::jsDynamicCast<JSServerWebSocket*>(JSValue::decode(value)); + if (!object) + return false; + + object->m_ctx = ptr; + return true; +} + + +extern "C" const size_t ServerWebSocket__ptrOffset = JSServerWebSocket::offsetOfWrapped(); + +void JSServerWebSocket::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) +{ + auto* thisObject = jsCast<JSServerWebSocket*>(cell); + if (void* wrapped = thisObject->wrapped()) { + // if (thisObject->scriptExecutionContext()) + // analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string()); + } + Base::analyzeHeap(cell, analyzer); +} + +JSObject* JSServerWebSocket::createPrototype(VM& vm, JSDOMGlobalObject* globalObject) +{ + return JSServerWebSocketPrototype::create(vm, globalObject, JSServerWebSocketPrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); +} + +template<typename Visitor> +void JSServerWebSocket::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + JSServerWebSocket* thisObject = jsCast<JSServerWebSocket*>(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + + + visitor.append(thisObject->m_data); + visitor.append(thisObject->m_remoteAddress); +} + +DEFINE_VISIT_CHILDREN(JSServerWebSocket);extern "C" void* TextDecoderClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsTextDecoderConstructor); extern "C" void TextDecoderClass__finalize(void*); @@ -2406,6 +2846,12 @@ JSC_DEFINE_CUSTOM_GETTER(TextDecoderPrototype__encodingGetterWrap, (JSGlobalObje thisObject->m_encoding.set(vm, thisObject, result); RELEASE_AND_RETURN(throwScope, JSValue::encode(result)); } +extern "C" void TextDecoderPrototype__encodingSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject *globalObject, JSC::EncodedJSValue value) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast<JSTextDecoder*>(JSValue::decode(thisValue)); + thisObject->m_encoding.set(vm, thisObject, JSValue::decode(value)); +} JSC_DEFINE_CUSTOM_GETTER(TextDecoderPrototype__fatalGetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) @@ -2738,6 +3184,12 @@ JSC_DEFINE_CUSTOM_GETTER(RequestPrototype__bodyGetterWrap, (JSGlobalObject * lex thisObject->m_body.set(vm, thisObject, result); RELEASE_AND_RETURN(throwScope, JSValue::encode(result)); } +extern "C" void RequestPrototype__bodySetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject *globalObject, JSC::EncodedJSValue value) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast<JSRequest*>(JSValue::decode(thisValue)); + thisObject->m_body.set(vm, thisObject, JSValue::decode(value)); +} JSC_DEFINE_CUSTOM_GETTER(RequestPrototype__bodyUsedGetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) @@ -2827,6 +3279,12 @@ JSC_DEFINE_CUSTOM_GETTER(RequestPrototype__headersGetterWrap, (JSGlobalObject * thisObject->m_headers.set(vm, thisObject, result); RELEASE_AND_RETURN(throwScope, JSValue::encode(result)); } +extern "C" void RequestPrototype__headersSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject *globalObject, JSC::EncodedJSValue value) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast<JSRequest*>(JSValue::decode(thisValue)); + thisObject->m_headers.set(vm, thisObject, JSValue::decode(value)); +} JSC_DEFINE_CUSTOM_GETTER(RequestPrototype__integrityGetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) @@ -2959,6 +3417,12 @@ JSC_DEFINE_CUSTOM_GETTER(RequestPrototype__urlGetterWrap, (JSGlobalObject * lexi thisObject->m_url.set(vm, thisObject, result); RELEASE_AND_RETURN(throwScope, JSValue::encode(result)); } +extern "C" void RequestPrototype__urlSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject *globalObject, JSC::EncodedJSValue value) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast<JSRequest*>(JSValue::decode(thisValue)); + thisObject->m_url.set(vm, thisObject, JSValue::decode(value)); +} void JSRequestPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) @@ -3266,6 +3730,12 @@ JSC_DEFINE_CUSTOM_GETTER(ResponsePrototype__bodyGetterWrap, (JSGlobalObject * le thisObject->m_body.set(vm, thisObject, result); RELEASE_AND_RETURN(throwScope, JSValue::encode(result)); } +extern "C" void ResponsePrototype__bodySetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject *globalObject, JSC::EncodedJSValue value) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast<JSResponse*>(JSValue::decode(thisValue)); + thisObject->m_body.set(vm, thisObject, JSValue::decode(value)); +} JSC_DEFINE_CUSTOM_GETTER(ResponsePrototype__bodyUsedGetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) @@ -3316,6 +3786,12 @@ JSC_DEFINE_CUSTOM_GETTER(ResponsePrototype__headersGetterWrap, (JSGlobalObject * thisObject->m_headers.set(vm, thisObject, result); RELEASE_AND_RETURN(throwScope, JSValue::encode(result)); } +extern "C" void ResponsePrototype__headersSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject *globalObject, JSC::EncodedJSValue value) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast<JSResponse*>(JSValue::decode(thisValue)); + thisObject->m_headers.set(vm, thisObject, JSValue::decode(value)); +} JSC_DEFINE_HOST_FUNCTION(ResponsePrototype__jsonCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) @@ -3392,6 +3868,12 @@ JSC_DEFINE_CUSTOM_GETTER(ResponsePrototype__statusTextGetterWrap, (JSGlobalObjec thisObject->m_statusText.set(vm, thisObject, result); RELEASE_AND_RETURN(throwScope, JSValue::encode(result)); } +extern "C" void ResponsePrototype__statusTextSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject *globalObject, JSC::EncodedJSValue value) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast<JSResponse*>(JSValue::decode(thisValue)); + thisObject->m_statusText.set(vm, thisObject, JSValue::decode(value)); +} JSC_DEFINE_HOST_FUNCTION(ResponsePrototype__textCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) @@ -3442,6 +3924,12 @@ JSC_DEFINE_CUSTOM_GETTER(ResponsePrototype__urlGetterWrap, (JSGlobalObject * lex thisObject->m_url.set(vm, thisObject, result); RELEASE_AND_RETURN(throwScope, JSValue::encode(result)); } +extern "C" void ResponsePrototype__urlSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject *globalObject, JSC::EncodedJSValue value) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast<JSResponse*>(JSValue::decode(thisValue)); + thisObject->m_url.set(vm, thisObject, JSValue::decode(value)); +} void JSResponsePrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) diff --git a/src/bun.js/bindings/ZigGeneratedClasses.h b/src/bun.js/bindings/ZigGeneratedClasses.h index b51637efa..45e47f81a 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.h +++ b/src/bun.js/bindings/ZigGeneratedClasses.h @@ -1133,6 +1133,131 @@ class JSSHA512_256Prototype final : public JSC::JSNonFinalObject { void finishCreation(JSC::VM&, JSC::JSGlobalObject* globalObject, JSSHA512_256Prototype* prototype); }; +class JSServerWebSocket final : public JSC::JSDestructibleObject { + public: + using Base = JSC::JSDestructibleObject; + static JSServerWebSocket* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx); + + DECLARE_EXPORT_INFO; + template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl<JSServerWebSocket, WebCore::UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForServerWebSocket.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForServerWebSocket = WTFMove(space); }, + [](auto& spaces) { return spaces.m_subspaceForServerWebSocket.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForServerWebSocket = WTFMove(space); }); + } + + static void destroy(JSC::JSCell*); + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(static_cast<JSC::JSType>(0b11101110), StructureFlags), info()); + } + + static JSObject* createPrototype(VM& vm, JSDOMGlobalObject* globalObject); + + ~JSServerWebSocket(); + + void* wrapped() const { return m_ctx; } + + void detach() + { + m_ctx = nullptr; + } + + static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&); + static ptrdiff_t offsetOfWrapped() { return OBJECT_OFFSETOF(JSServerWebSocket, m_ctx); } + + void* m_ctx { nullptr }; + + + JSServerWebSocket(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) + : Base(vm, structure) + { + m_ctx = sinkPtr; + } + + void finishCreation(JSC::VM&); + + DECLARE_VISIT_CHILDREN; + + mutable JSC::WriteBarrier<JSC::Unknown> m_data; +mutable JSC::WriteBarrier<JSC::Unknown> m_remoteAddress; + }; +class JSServerWebSocketPrototype final : public JSC::JSNonFinalObject { + public: + using Base = JSC::JSNonFinalObject; + + static JSServerWebSocketPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSServerWebSocketPrototype* ptr = new (NotNull, JSC::allocateCell<JSServerWebSocketPrototype>(vm)) JSServerWebSocketPrototype(vm, globalObject, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + template<typename CellType, JSC::SubspaceAccess> + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + + private: + JSServerWebSocketPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); + }; + + class JSServerWebSocketConstructor final : public JSC::InternalFunction { + public: + using Base = JSC::InternalFunction; + static JSServerWebSocketConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSServerWebSocketPrototype* prototype); + + static constexpr unsigned StructureFlags = Base::StructureFlags; + static constexpr bool needsDestruction = false; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); + } + + template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl<JSServerWebSocketConstructor, WebCore::UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForServerWebSocketConstructor.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForServerWebSocketConstructor = WTFMove(space); }, + [](auto& spaces) { return spaces.m_subspaceForServerWebSocketConstructor.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForServerWebSocketConstructor = WTFMove(space); }); + } + + + void initializeProperties(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSServerWebSocketPrototype* prototype); + + // Must be defined for each specialization class. + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); + DECLARE_EXPORT_INFO; + private: + JSServerWebSocketConstructor(JSC::VM& vm, JSC::Structure* structure, JSC::NativeFunction nativeFunction) + : Base(vm, structure, nativeFunction, nativeFunction) + + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject* globalObject, JSServerWebSocketPrototype* prototype); + }; class JSTextDecoder final : public JSC::JSDestructibleObject { public: using Base = JSC::JSDestructibleObject; diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig index 124602918..80ef7d873 100644 --- a/src/bun.js/bindings/generated_classes.zig +++ b/src/bun.js/bindings/generated_classes.zig @@ -20,6 +20,30 @@ pub const JSSubprocess = struct { return Subprocess__fromJS(value); } + extern fn SubprocessPrototype__stderrSetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void; + + /// Set the cached value for stderr on Subprocess + /// This value will be visited by the garbage collector. + pub fn stderrSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { + SubprocessPrototype__stderrSetCachedValue(thisValue, globalObject, value); + } + + extern fn SubprocessPrototype__stdinSetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void; + + /// Set the cached value for stdin on Subprocess + /// This value will be visited by the garbage collector. + pub fn stdinSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { + SubprocessPrototype__stdinSetCachedValue(thisValue, globalObject, value); + } + + extern fn SubprocessPrototype__stdoutSetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void; + + /// Set the cached value for stdout on Subprocess + /// This value will be visited by the garbage collector. + pub fn stdoutSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { + SubprocessPrototype__stdoutSetCachedValue(thisValue, globalObject, value); + } + /// Get the Subprocess constructor value. /// This loads lazily from the global object. pub fn getConstructor(globalObject: *JSC.JSGlobalObject) JSC.JSValue { @@ -726,6 +750,115 @@ pub const JSSHA512_256 = struct { } } }; +pub const JSServerWebSocket = struct { + const ServerWebSocket = Classes.ServerWebSocket; + const GetterType = fn (*ServerWebSocket, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; + const SetterType = fn (*ServerWebSocket, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool; + const CallbackType = fn (*ServerWebSocket, *JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) JSC.JSValue; + + /// Return the pointer to the wrapped object. + /// If the object does not match the type, return null. + pub fn fromJS(value: JSC.JSValue) ?*ServerWebSocket { + JSC.markBinding(); + return ServerWebSocket__fromJS(value); + } + + extern fn ServerWebSocketPrototype__dataSetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void; + + /// Set the cached value for data on ServerWebSocket + /// This value will be visited by the garbage collector. + pub fn dataSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { + ServerWebSocketPrototype__dataSetCachedValue(thisValue, globalObject, value); + } + + extern fn ServerWebSocketPrototype__remoteAddressSetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void; + + /// Set the cached value for remoteAddress on ServerWebSocket + /// This value will be visited by the garbage collector. + pub fn remoteAddressSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { + ServerWebSocketPrototype__remoteAddressSetCachedValue(thisValue, globalObject, value); + } + + /// Get the ServerWebSocket constructor value. + /// This loads lazily from the global object. + pub fn getConstructor(globalObject: *JSC.JSGlobalObject) JSC.JSValue { + JSC.markBinding(); + return ServerWebSocket__getConstructor(globalObject); + } + + /// Create a new instance of ServerWebSocket + pub fn toJS(this: *ServerWebSocket, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + JSC.markBinding(); + if (comptime Environment.allow_assert) { + const value__ = ServerWebSocket__create(globalObject, this); + std.debug.assert(value__.as(ServerWebSocket).? == this); // If this fails, likely a C ABI issue. + return value__; + } else { + return ServerWebSocket__create(globalObject, this); + } + } + + /// Modify the internal ptr to point to a new instance of ServerWebSocket. + pub fn dangerouslySetPtr(value: JSC.JSValue, ptr: ?*ServerWebSocket) bool { + JSC.markBinding(); + return ServerWebSocket__dangerouslySetPtr(value, ptr); + } + + extern fn ServerWebSocket__fromJS(JSC.JSValue) ?*ServerWebSocket; + extern fn ServerWebSocket__getConstructor(*JSC.JSGlobalObject) JSC.JSValue; + + extern fn ServerWebSocket__create(globalObject: *JSC.JSGlobalObject, ptr: ?*ServerWebSocket) JSC.JSValue; + + extern fn ServerWebSocket__dangerouslySetPtr(JSC.JSValue, ?*ServerWebSocket) bool; + + comptime { + if (@TypeOf(ServerWebSocket.constructor) != (fn (*JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) ?*ServerWebSocket)) { + @compileLog("ServerWebSocket.constructor is not a constructor"); + } + + if (@TypeOf(ServerWebSocket.finalize) != (fn (*ServerWebSocket) callconv(.C) void)) { + @compileLog("ServerWebSocket.finalize is not a finalizer"); + } + + if (@TypeOf(ServerWebSocket.close) != CallbackType) + @compileLog("Expected ServerWebSocket.close to be a callback"); + if (@TypeOf(ServerWebSocket.getData) != GetterType) + @compileLog("Expected ServerWebSocket.getData to be a getter"); + + if (@TypeOf(ServerWebSocket.getBufferedAmount) != CallbackType) + @compileLog("Expected ServerWebSocket.getBufferedAmount to be a callback"); + if (@TypeOf(ServerWebSocket.isSubscribed) != CallbackType) + @compileLog("Expected ServerWebSocket.isSubscribed to be a callback"); + if (@TypeOf(ServerWebSocket.publish) != CallbackType) + @compileLog("Expected ServerWebSocket.publish to be a callback"); + if (@TypeOf(ServerWebSocket.getReadyState) != GetterType) + @compileLog("Expected ServerWebSocket.getReadyState to be a getter"); + + if (@TypeOf(ServerWebSocket.getRemoteAddress) != GetterType) + @compileLog("Expected ServerWebSocket.getRemoteAddress to be a getter"); + + if (@TypeOf(ServerWebSocket.send) != CallbackType) + @compileLog("Expected ServerWebSocket.send to be a callback"); + if (@TypeOf(ServerWebSocket.subscribe) != CallbackType) + @compileLog("Expected ServerWebSocket.subscribe to be a callback"); + if (@TypeOf(ServerWebSocket.unsubscribe) != CallbackType) + @compileLog("Expected ServerWebSocket.unsubscribe to be a callback"); + if (!JSC.is_bindgen) { + @export(ServerWebSocket.close, .{ .name = "ServerWebSocketPrototype__close" }); + @export(ServerWebSocket.constructor, .{ .name = "ServerWebSocketClass__construct" }); + @export(ServerWebSocket.finalize, .{ .name = "ServerWebSocketClass__finalize" }); + @export(ServerWebSocket.getBufferedAmount, .{ .name = "ServerWebSocketPrototype__getBufferedAmount" }); + @export(ServerWebSocket.getData, .{ .name = "ServerWebSocketPrototype__getData" }); + @export(ServerWebSocket.getReadyState, .{ .name = "ServerWebSocketPrototype__getReadyState" }); + @export(ServerWebSocket.getRemoteAddress, .{ .name = "ServerWebSocketPrototype__getRemoteAddress" }); + @export(ServerWebSocket.isSubscribed, .{ .name = "ServerWebSocketPrototype__isSubscribed" }); + @export(ServerWebSocket.publish, .{ .name = "ServerWebSocketPrototype__publish" }); + @export(ServerWebSocket.send, .{ .name = "ServerWebSocketPrototype__send" }); + @export(ServerWebSocket.subscribe, .{ .name = "ServerWebSocketPrototype__subscribe" }); + @export(ServerWebSocket.unsubscribe, .{ .name = "ServerWebSocketPrototype__unsubscribe" }); + } + } +}; pub const JSTextDecoder = struct { const TextDecoder = Classes.TextDecoder; const GetterType = fn (*TextDecoder, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; @@ -739,6 +872,14 @@ pub const JSTextDecoder = struct { return TextDecoder__fromJS(value); } + extern fn TextDecoderPrototype__encodingSetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void; + + /// Set the cached value for encoding on TextDecoder + /// This value will be visited by the garbage collector. + pub fn encodingSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { + TextDecoderPrototype__encodingSetCachedValue(thisValue, globalObject, value); + } + /// Get the TextDecoder constructor value. /// This loads lazily from the global object. pub fn getConstructor(globalObject: *JSC.JSGlobalObject) JSC.JSValue { @@ -810,6 +951,30 @@ pub const JSRequest = struct { return Request__fromJS(value); } + extern fn RequestPrototype__bodySetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void; + + /// Set the cached value for body on Request + /// This value will be visited by the garbage collector. + pub fn bodySetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { + RequestPrototype__bodySetCachedValue(thisValue, globalObject, value); + } + + extern fn RequestPrototype__headersSetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void; + + /// Set the cached value for headers on Request + /// This value will be visited by the garbage collector. + pub fn headersSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { + RequestPrototype__headersSetCachedValue(thisValue, globalObject, value); + } + + extern fn RequestPrototype__urlSetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void; + + /// Set the cached value for url on Request + /// This value will be visited by the garbage collector. + pub fn urlSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { + RequestPrototype__urlSetCachedValue(thisValue, globalObject, value); + } + /// Get the Request constructor value. /// This loads lazily from the global object. pub fn getConstructor(globalObject: *JSC.JSGlobalObject) JSC.JSValue { @@ -942,6 +1107,38 @@ pub const JSResponse = struct { return Response__fromJS(value); } + extern fn ResponsePrototype__bodySetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void; + + /// Set the cached value for body on Response + /// This value will be visited by the garbage collector. + pub fn bodySetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { + ResponsePrototype__bodySetCachedValue(thisValue, globalObject, value); + } + + extern fn ResponsePrototype__headersSetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void; + + /// Set the cached value for headers on Response + /// This value will be visited by the garbage collector. + pub fn headersSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { + ResponsePrototype__headersSetCachedValue(thisValue, globalObject, value); + } + + extern fn ResponsePrototype__statusTextSetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void; + + /// Set the cached value for statusText on Response + /// This value will be visited by the garbage collector. + pub fn statusTextSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { + ResponsePrototype__statusTextSetCachedValue(thisValue, globalObject, value); + } + + extern fn ResponsePrototype__urlSetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void; + + /// Set the cached value for url on Response + /// This value will be visited by the garbage collector. + pub fn urlSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { + ResponsePrototype__urlSetCachedValue(thisValue, globalObject, value); + } + /// Get the Response constructor value. /// This loads lazily from the global object. pub fn getConstructor(globalObject: *JSC.JSGlobalObject) JSC.JSValue { @@ -1154,6 +1351,7 @@ comptime { _ = JSSHA384; _ = JSSHA256; _ = JSSHA512_256; + _ = JSServerWebSocket; _ = JSTextDecoder; _ = JSRequest; _ = JSResponse; diff --git a/src/bun.js/bindings/generated_classes_list.zig b/src/bun.js/bindings/generated_classes_list.zig index dbfa6c792..0f74343f6 100644 --- a/src/bun.js/bindings/generated_classes_list.zig +++ b/src/bun.js/bindings/generated_classes_list.zig @@ -14,4 +14,5 @@ pub const Classes = struct { pub const TextDecoder = JSC.WebCore.TextDecoder; pub const Blob = JSC.WebCore.Blob; pub const Subprocess = JSC.Subprocess; + pub const ServerWebSocket = JSC.API.ServerWebSocket; }; diff --git a/src/bun.js/bindings/headers-cpp.h b/src/bun.js/bindings/headers-cpp.h index a22987a0c..b64b6cbd0 100644 --- a/src/bun.js/bindings/headers-cpp.h +++ b/src/bun.js/bindings/headers-cpp.h @@ -1,4 +1,4 @@ -//-- AUTOGENERATED FILE -- 1665267248 +//-- AUTOGENERATED FILE -- 1665309067 // clang-format off #pragma once diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h index f06d534fb..85e865618 100644 --- a/src/bun.js/bindings/headers.h +++ b/src/bun.js/bindings/headers.h @@ -1,5 +1,5 @@ // clang-format off -//-- AUTOGENERATED FILE -- 1665267248 +//-- AUTOGENERATED FILE -- 1665309067 #pragma once #include <stddef.h> diff --git a/src/deps/_libusockets.h b/src/deps/_libusockets.h index 75090aba9..157275686 100644 --- a/src/deps/_libusockets.h +++ b/src/deps/_libusockets.h @@ -90,9 +90,10 @@ typedef void (*uws_websocket_ping_pong_handler)(uws_websocket_t *ws, size_t length); typedef void (*uws_websocket_close_handler)(uws_websocket_t *ws, int code, const char *message, size_t length); -typedef void (*uws_websocket_upgrade_handler)(uws_res_t *response, +typedef void (*uws_websocket_upgrade_handler)(void *, uws_res_t *response, uws_req_t *request, - uws_socket_context_t *context); + uws_socket_context_t *context, + size_t id); typedef struct { uws_compress_options_t compression; @@ -177,8 +178,9 @@ void uws_filter(int ssl, uws_app_t *app, uws_filter_handler handler, void *user_data); // WebSocket -void uws_ws(int ssl, uws_app_t *app, const char *pattern, - uws_socket_behavior_t behavior); +void uws_ws(int ssl, uws_app_t *app, void *upgradeCtx, const char *pattern, + size_t pattern_length, size_t id, + const uws_socket_behavior_t *behavior); void *uws_ws_get_user_data(int ssl, uws_websocket_t *ws); void uws_ws_close(int ssl, uws_websocket_t *ws); uws_sendstatus_t uws_ws_send(int ssl, uws_websocket_t *ws, const char *message, diff --git a/src/deps/libuwsockets.cpp b/src/deps/libuwsockets.cpp index 5acfead19..d542b7ecb 100644 --- a/src/deps/libuwsockets.cpp +++ b/src/deps/libuwsockets.cpp @@ -339,8 +339,11 @@ void uws_filter(int ssl, uws_app_t *app, uws_filter_handler handler, } } -void uws_ws(int ssl, uws_app_t *app, const char *pattern, - uws_socket_behavior_t behavior) { +void uws_ws(int ssl, uws_app_t *app, void *upgradeContext, const char *pattern, + size_t pattern_length, size_t id, + const uws_socket_behavior_t *behavior_) { + uws_socket_behavior_t behavior = *behavior_; + if (ssl) { auto generic_handler = uWS::SSLApp::WebSocketBehavior<void *>{ .compression = (uWS::CompressOptions)(uint64_t)behavior.compression, @@ -354,10 +357,10 @@ void uws_ws(int ssl, uws_app_t *app, const char *pattern, }; if (behavior.upgrade) - generic_handler.upgrade = [behavior](auto *res, auto *req, - auto *context) { - behavior.upgrade((uws_res_t *)res, (uws_req_t *)req, - (uws_socket_context_t *)context); + generic_handler.upgrade = [behavior, upgradeContext, + id](auto *res, auto *req, auto *context) { + behavior.upgrade(upgradeContext, (uws_res_t *)res, (uws_req_t *)req, + (uws_socket_context_t *)context, id); }; if (behavior.open) generic_handler.open = [behavior](auto *ws) { @@ -388,7 +391,8 @@ void uws_ws(int ssl, uws_app_t *app, const char *pattern, }; uWS::SSLApp *uwsApp = (uWS::SSLApp *)app; - uwsApp->ws<void *>(pattern, std::move(generic_handler)); + uwsApp->ws<void *>(std::string(pattern, pattern_length), + std::move(generic_handler)); } else { auto generic_handler = uWS::App::WebSocketBehavior<void *>{ .compression = (uWS::CompressOptions)(uint64_t)behavior.compression, @@ -402,10 +406,10 @@ void uws_ws(int ssl, uws_app_t *app, const char *pattern, }; if (behavior.upgrade) - generic_handler.upgrade = [behavior](auto *res, auto *req, - auto *context) { - behavior.upgrade((uws_res_t *)res, (uws_req_t *)req, - (uws_socket_context_t *)context); + generic_handler.upgrade = [behavior, upgradeContext, + id](auto *res, auto *req, auto *context) { + behavior.upgrade(upgradeContext, (uws_res_t *)res, (uws_req_t *)req, + (uws_socket_context_t *)context, id); }; if (behavior.open) generic_handler.open = [behavior](auto *ws) { @@ -435,7 +439,8 @@ void uws_ws(int ssl, uws_app_t *app, const char *pattern, message.length()); }; uWS::App *uwsApp = (uWS::App *)app; - uwsApp->ws<void *>(pattern, std::move(generic_handler)); + uwsApp->ws<void *>(std::string(pattern, pattern_length), + std::move(generic_handler)); } } diff --git a/src/deps/uws.zig b/src/deps/uws.zig index d2940f43f..51fa14e24 100644 --- a/src/deps/uws.zig +++ b/src/deps/uws.zig @@ -573,28 +573,229 @@ pub const uws_app_t = uws_app_s; pub const uws_socket_context_s = opaque {}; pub const uws_socket_context_t = uws_socket_context_s; +pub const AnyWebSocket = union(enum) { + ssl: *NewApp(true).WebSocket, + tcp: *NewApp(false).WebSocket, + + pub fn raw(this: AnyWebSocket) *RawWebSocket { + return switch (this) { + .ssl => this.ssl.raw(), + .tcp => this.tcp.raw(), + }; + } + pub fn as(this: AnyWebSocket, comptime Type: type) ?*Type { + @setRuntimeSafety(false); + return switch (this) { + .ssl => this.ssl.as(Type), + .tcp => this.tcp.as(Type), + }; + } + + pub fn close(this: AnyWebSocket) void { + const ssl_flag = @boolToInt(this == .ssl); + return uws_ws_close(ssl_flag, this.raw()); + } + + pub fn send(this: AnyWebSocket, message: []const u8, opcode: Opcode, compress: bool, fin: bool) SendStatus { + return switch (this) { + .ssl => uws_ws_send_with_options(1, this.ssl.raw(), message.ptr, message.len, opcode, compress, fin), + .tcp => uws_ws_send_with_options(0, this.tcp.raw(), message.ptr, message.len, opcode, compress, fin), + }; + } + pub fn sendLastFragment(this: AnyWebSocket, message: []const u8, compress: bool) SendStatus { + switch (this) { + .tcp => return uws_ws_send_last_fragment(0, this.raw(), message.ptr, message.len, compress), + .ssl => return uws_ws_send_last_fragment(1, this.raw(), message.ptr, message.len, compress), + } + } + pub fn end(this: AnyWebSocket, code: i32, message: []const u8) void { + switch (this) { + .tcp => uws_ws_end(1, this.tcp.raw(), code, message.ptr, message.len), + .ssl => uws_ws_end(0, this.ssl.raw(), code, message.ptr, message.len), + } + } + pub fn cork(this: AnyWebSocket, ctx: anytype, comptime callback: anytype) void { + const ContextType = @TypeOf(ctx); + const Wrapper = struct { + pub fn wrap(user_data: ?*anyopaque) callconv(.C) void { + @call(.{ .modifier = .always_inline }, callback, .{bun.cast(ContextType, user_data.?)}); + } + }; + + switch (this) { + .ssl => uws_ws_cork(1, this.raw(), Wrapper.wrap, ctx), + .tcp => uws_ws_cork(0, this.raw(), Wrapper.wrap, ctx), + } + } + pub fn subscribe(this: AnyWebSocket, topic: []const u8) bool { + return switch (this) { + .ssl => uws_ws_subscribe(0, this.ssl.raw(), topic.ptr, topic.len), + .tcp => uws_ws_subscribe(1, this.tcp.raw(), topic.ptr, topic.len), + }; + } + pub fn unsubscribe(this: AnyWebSocket, topic: []const u8) bool { + return switch (this) { + .ssl => uws_ws_unsubscribe(1, this.raw(), topic.ptr, topic.len), + .tcp => uws_ws_unsubscribe(0, this.raw(), topic.ptr, topic.len), + }; + } + pub fn isSubscribed(this: AnyWebSocket, topic: []const u8) bool { + return switch (this) { + .ssl => uws_ws_is_subscribed(1, this.raw(), topic.ptr, topic.len), + .tcp => uws_ws_is_subscribed(0, this.raw(), topic.ptr, topic.len), + }; + } + // pub fn iterateTopics(this: AnyWebSocket) { + // return uws_ws_iterate_topics(ssl_flag, this.raw(), callback: ?fn ([*c]const u8, usize, ?*anyopaque) callconv(.C) void, user_data: ?*anyopaque) void; + // } + pub fn publish(this: AnyWebSocket, topic: []const u8, message: []const u8) bool { + return switch (this) { + .ssl => uws_ws_publish(1, this.ssl.raw(), topic.ptr, topic.len, message.ptr, message.len), + .tcp => uws_ws_publish(0, this.tcp.raw(), topic.ptr, topic.len, message.ptr, message.len), + }; + } + pub fn publishWithOptions(this: AnyWebSocket, topic: []const u8, message: []const u8, opcode: Opcode, compress: bool) bool { + return switch (this) { + .ssl => uws_ws_publish_with_options(1, this.ssl.raw(), topic.ptr, topic.len, message.ptr, message.len, opcode, compress), + .tcp => uws_ws_publish_with_options(0, this.tcp.raw(), topic.ptr, topic.len, message.ptr, message.len, opcode, compress), + }; + } + pub fn getBufferedAmount(this: AnyWebSocket) u32 { + return switch (this) { + .ssl => uws_ws_get_buffered_amount(1, this.ssl.raw()), + .tcp => uws_ws_get_buffered_amount(0, this.tcp.raw()), + }; + } + + pub fn getRemoteAddress(this: AnyWebSocket, buf: []u8) []u8 { + return switch (this) { + .ssl => this.ssl.getRemoteAddressAsText(buf), + .tcp => this.tcp.getRemoteAddressAsText(buf), + }; + } +}; + pub const RawWebSocket = opaque {}; -pub const uws_websocket_handler = ?fn (?*RawWebSocket) callconv(.C) void; -pub const uws_websocket_message_handler = ?fn (?*RawWebSocket, [*c]const u8, usize, uws_opcode_t) callconv(.C) void; -pub const uws_websocket_ping_pong_handler = ?fn (?*RawWebSocket, [*c]const u8, usize) callconv(.C) void; -pub const uws_websocket_close_handler = ?fn (?*RawWebSocket, i32, [*c]const u8, usize) callconv(.C) void; -pub const uws_websocket_upgrade_handler = ?fn (*uws_res, ?*Request, ?*uws_socket_context_t) callconv(.C) void; -pub const uws_socket_behavior_t = extern struct { - compression: uws_compress_options_t, - maxPayloadLength: c_uint, - idleTimeout: c_ushort, - maxBackpressure: c_uint, - closeOnBackpressureLimit: bool, - resetIdleTimeoutOnSend: bool, - sendPingsAutomatically: bool, - maxLifetime: c_ushort, - upgrade: uws_websocket_upgrade_handler, - open: uws_websocket_handler, - message: uws_websocket_message_handler, - drain: uws_websocket_handler, - ping: uws_websocket_ping_pong_handler, - pong: uws_websocket_ping_pong_handler, - close: uws_websocket_close_handler, + +pub const uws_websocket_handler = ?fn (*RawWebSocket) callconv(.C) void; +pub const uws_websocket_message_handler = ?fn (*RawWebSocket, [*c]const u8, usize, Opcode) callconv(.C) void; +pub const uws_websocket_close_handler = ?fn (*RawWebSocket, i32, [*c]const u8, usize) callconv(.C) void; +pub const uws_websocket_upgrade_handler = ?fn (*anyopaque, *uws_res, *Request, *uws_socket_context_t, usize) callconv(.C) void; + +pub const uws_websocket_ping_pong_handler = ?fn (*RawWebSocket, [*c]const u8, usize) callconv(.C) void; + +pub const WebSocketBehavior = extern struct { + compression: uws_compress_options_t = 0, + maxPayloadLength: c_uint = std.math.maxInt(u32), + idleTimeout: c_ushort = 120, + maxBackpressure: c_uint = 1024 * 1024, + closeOnBackpressureLimit: bool = false, + resetIdleTimeoutOnSend: bool = true, + sendPingsAutomatically: bool = true, + maxLifetime: c_ushort = 0, + upgrade: uws_websocket_upgrade_handler = null, + open: uws_websocket_handler = null, + message: uws_websocket_message_handler = null, + drain: uws_websocket_handler = null, + ping: uws_websocket_ping_pong_handler = null, + pong: uws_websocket_ping_pong_handler = null, + close: uws_websocket_close_handler = null, + + pub fn Wrap( + comptime ServerType: type, + comptime Type: type, + comptime ssl: bool, + ) type { + return extern struct { + const is_ssl = ssl; + const WebSocket = NewApp(is_ssl).WebSocket; + const Server = ServerType; + + const active_field_name = if (is_ssl) "ssl" else "tcp"; + + pub fn _open(raw_ws: *RawWebSocket) callconv(.C) void { + var ws = @unionInit(AnyWebSocket, active_field_name, @ptrCast(*WebSocket, raw_ws)); + var this = ws.as(Type).?; + @call(.{ .modifier = .always_inline }, Type.onOpen, .{ this, ws }); + } + pub fn _message(raw_ws: *RawWebSocket, message: [*c]const u8, length: usize, opcode: Opcode) callconv(.C) void { + var ws = @unionInit(AnyWebSocket, active_field_name, @ptrCast(*WebSocket, raw_ws)); + var this = ws.as(Type).?; + @call( + .{ .modifier = .always_inline }, + Type.onMessage, + .{ this, ws, if (length > 0) message.?[0..length] else "", opcode }, + ); + } + pub fn _drain(raw_ws: *RawWebSocket) callconv(.C) void { + var ws = @unionInit(AnyWebSocket, active_field_name, @ptrCast(*WebSocket, raw_ws)); + var this = ws.as(Type).?; + @call(.{ .modifier = .always_inline }, Type.onDrain, .{ + this, + ws, + }); + } + pub fn _ping(raw_ws: *RawWebSocket, message: [*c]const u8, length: usize) callconv(.C) void { + var ws = @unionInit(AnyWebSocket, active_field_name, @ptrCast(*WebSocket, raw_ws)); + var this = ws.as(Type).?; + @call(.{ .modifier = .always_inline }, Type.onPing, .{ + this, + ws, + if (length > 0) message.?[0..length] else "", + }); + } + pub fn _pong(raw_ws: *RawWebSocket, message: [*c]const u8, length: usize) callconv(.C) void { + var ws = @unionInit(AnyWebSocket, active_field_name, @ptrCast(*WebSocket, raw_ws)); + var this = ws.as(Type).?; + @call(.{ .modifier = .always_inline }, Type.onPong, .{ + this, + ws, + if (length > 0) message.?[0..length] else "", + }); + } + pub fn _close(raw_ws: *RawWebSocket, code: i32, message: [*c]const u8, length: usize) callconv(.C) void { + var ws = @unionInit(AnyWebSocket, active_field_name, @ptrCast(*WebSocket, raw_ws)); + var this = ws.as(Type).?; + @call( + .{ .modifier = .always_inline }, + Type.onClose, + .{ + this, + ws, + code, + if (length > 0) message.?[0..length] else "", + }, + ); + } + pub fn _upgrade(ptr: *anyopaque, res: *uws_res, req: *Request, context: *uws_socket_context_t, id: usize) callconv(.C) void { + @call( + .{ .modifier = .always_inline }, + Server.onWebSocketUpgrade, + .{ bun.cast(*Server, ptr), @ptrCast(*NewApp(is_ssl).Response, res), req, context, id }, + ); + } + + pub fn apply(behavior: WebSocketBehavior) WebSocketBehavior { + return WebSocketBehavior{ + .compression = behavior.compression, + .maxPayloadLength = behavior.maxPayloadLength, + .idleTimeout = behavior.idleTimeout, + .maxBackpressure = behavior.maxBackpressure, + .closeOnBackpressureLimit = behavior.closeOnBackpressureLimit, + .resetIdleTimeoutOnSend = behavior.resetIdleTimeoutOnSend, + .sendPingsAutomatically = behavior.sendPingsAutomatically, + .maxLifetime = behavior.maxLifetime, + .upgrade = _upgrade, + .open = _open, + .message = _message, + .drain = _drain, + .ping = _ping, + .pong = _pong, + .close = _close, + }; + } + }; + } }; pub const uws_listen_handler = ?fn (?*listen_socket_t, ?*anyopaque) callconv(.C) void; pub const uws_method_handler = ?fn (*uws_res, *Request, ?*anyopaque) callconv(.C) void; @@ -886,7 +1087,7 @@ pub fn NewApp(comptime ssl: bool) type { pub fn num_subscribers(app: *ThisApp, topic: [:0]const u8) c_uint { return uws_num_subscribers(ssl_flag, @ptrCast(*uws_app_t, app), topic); } - pub fn publish(app: *ThisApp, topic: []const u8, message: []const u8, opcode: uws_opcode_t, compress: bool) bool { + pub fn publish(app: *ThisApp, topic: []const u8, message: []const u8, opcode: Opcode, compress: bool) bool { return uws_publish(ssl_flag, @ptrCast(*uws_app_t, app), topic.ptr, topic.len, message.ptr, message.len, opcode, compress); } pub fn getNativeHandle(app: *ThisApp) ?*anyopaque { @@ -907,8 +1108,9 @@ pub fn NewApp(comptime ssl: bool) type { pub fn filter(app: *ThisApp, handler: uws_filter_handler, user_data: ?*anyopaque) void { return uws_filter(ssl_flag, @ptrCast(*uws_app_t, app), handler, user_data); } - pub fn ws(app: *ThisApp, pattern: [:0]const u8, behavior: uws_socket_behavior_t) void { - return uws_ws(ssl_flag, @ptrCast(*uws_app_t, app), pattern, behavior); + pub fn ws(app: *ThisApp, pattern: []const u8, ctx: *anyopaque, id: usize, behavior_: WebSocketBehavior) void { + var behavior = behavior_; + uws_ws(ssl_flag, @ptrCast(*uws_app_t, app), ctx, pattern.ptr, pattern.len, id, &behavior); } pub const Response = opaque { @@ -1158,78 +1360,102 @@ pub fn NewApp(comptime ssl: bool) type { uws_res_write_headers(ssl_flag, res.downcast(), names.ptr, values.ptr, values.len, buf.ptr); } - pub const WebSocket = opaque { - pub fn raw(this: *WebSocket) *RawWebSocket { - return @ptrCast(*RawWebSocket, this); - } - pub fn as(this: *WebSocket, comptime Type: type) ?*Type { - @setRuntimeSafety(false); - return @ptrCast(*?Type, @alignCast(@alignOf(Type), uws_ws_get_user_data(this))).*; - } + pub fn upgrade( + res: *Response, + comptime Data: type, + data: Data, + sec_web_socket_key: []const u8, + sec_web_socket_protocol: []const u8, + sec_web_socket_extensions: []const u8, + ctx: ?*uws_socket_context_t, + ) void { + uws_res_upgrade( + ssl_flag, + res.downcast(), + data, + sec_web_socket_key.ptr, + sec_web_socket_key.len, + sec_web_socket_protocol.ptr, + sec_web_socket_protocol.len, + sec_web_socket_extensions.ptr, + sec_web_socket_extensions.len, + ctx, + ); + } + }; - pub fn close(this: *WebSocket) void { - return uws_ws_close(ssl_flag, this.raw()); - } - pub fn send(this: *WebSocket, message: []const u8, opcode: uws_opcode_t) SendStatus { - return uws_ws_send(ssl_flag, this.raw(), message.ptr, message.len, opcode); - } - pub fn sendWithOptions(this: *WebSocket, message: []const u8, opcode: uws_opcode_t, compress: bool, fin: bool) SendStatus { - return uws_ws_send_with_options(ssl_flag, this.raw(), message.ptr, message.len, opcode, compress, fin); - } - // pub fn sendFragment(this: *WebSocket, message: []const u8) SendStatus { - // return uws_ws_send_fragment(ssl_flag, this.raw(), message: [*c]const u8, length: usize, compress: bool); - // } - // pub fn sendFirstFragment(this: *WebSocket, message: []const u8) SendStatus { - // return uws_ws_send_first_fragment(ssl_flag, this.raw(), message: [*c]const u8, length: usize, compress: bool); - // } - // pub fn sendFirstFragmentWithOpcode(this: *WebSocket, message: []const u8, opcode: u32, compress: bool) SendStatus { - // return uws_ws_send_first_fragment_with_opcode(ssl_flag, this.raw(), message: [*c]const u8, length: usize, opcode: uws_opcode_t, compress: bool); - // } - pub fn sendLastFragment(this: *WebSocket, message: []const u8, compress: bool) SendStatus { - return uws_ws_send_last_fragment(ssl_flag, this.raw(), message.ptr, message.len, compress); - } - pub fn end(this: *WebSocket, code: i32, message: []const u8) void { - return uws_ws_end(ssl_flag, this.raw(), code, message.ptr, message.len); - } - pub fn cork(this: *WebSocket, ctx: anytype, comptime callback: anytype) void { - const ContextType = @TypeOf(ctx); - const Wrapper = struct { - pub fn wrap(user_data: ?*anyopaque) callconv(.C) void { - @call(.{ .modifier = .always_inline }, callback, .{bun.cast(ContextType, user_data.?)}); - } - }; + pub const WebSocket = opaque { + pub fn raw(this: *WebSocket) *RawWebSocket { + return @ptrCast(*RawWebSocket, this); + } + pub fn as(this: *WebSocket, comptime Type: type) ?*Type { + @setRuntimeSafety(false); + return @ptrCast(?*Type, @alignCast(@alignOf(Type), uws_ws_get_user_data(ssl_flag, this.raw()))); + } - return uws_ws_cork(ssl_flag, this.raw(), Wrapper.wrap, ctx); - } - pub fn subscribe(this: *WebSocket, topic: []const u8) bool { - return uws_ws_subscribe(ssl_flag, this.raw(), topic.ptr, topic.len); - } - pub fn unsubscribe(this: *WebSocket, topic: []const u8) bool { - return uws_ws_unsubscribe(ssl_flag, this.raw(), topic.ptr, topic.len); - } - pub fn isSubscribed(this: *WebSocket, topic: []const u8) bool { - return uws_ws_is_subscribed(ssl_flag, this.raw(), topic.ptr, topic.len); - } - // pub fn iterateTopics(this: *WebSocket) { - // return uws_ws_iterate_topics(ssl_flag, this.raw(), callback: ?fn ([*c]const u8, usize, ?*anyopaque) callconv(.C) void, user_data: ?*anyopaque) void; - // } - pub fn publish(this: *WebSocket, topic: []const u8, message: []const u8) bool { - return uws_ws_publish(ssl_flag, this.raw(), topic.ptr, topic.len, message.ptr, message.len); - } - pub fn publishWithOptions(this: *WebSocket, topic: []const u8, message: []const u8, opcode: uws_opcode_t, compress: bool) bool { - return uws_ws_publish_with_options(ssl_flag, this.raw(), topic.ptr, topic.len, message.ptr, message.len, opcode, compress); - } - pub fn getBufferedAmount(this: *WebSocket) u32 { - return uws_ws_get_buffered_amount(ssl_flag, this.raw()); - } - pub fn getRemoteAddress(this: *WebSocket, buf: []u8) []u8 { - return buf[0..uws_ws_get_remote_address(ssl_flag, this.raw(), &buf.ptr)]; - } + pub fn close(this: *WebSocket) void { + return uws_ws_close(ssl_flag, this.raw()); + } + pub fn send(this: *WebSocket, message: []const u8, opcode: Opcode) SendStatus { + return uws_ws_send(ssl_flag, this.raw(), message.ptr, message.len, opcode); + } + pub fn sendWithOptions(this: *WebSocket, message: []const u8, opcode: Opcode, compress: bool, fin: bool) SendStatus { + return uws_ws_send_with_options(ssl_flag, this.raw(), message.ptr, message.len, opcode, compress, fin); + } + // pub fn sendFragment(this: *WebSocket, message: []const u8) SendStatus { + // return uws_ws_send_fragment(ssl_flag, this.raw(), message: [*c]const u8, length: usize, compress: bool); + // } + // pub fn sendFirstFragment(this: *WebSocket, message: []const u8) SendStatus { + // return uws_ws_send_first_fragment(ssl_flag, this.raw(), message: [*c]const u8, length: usize, compress: bool); + // } + // pub fn sendFirstFragmentWithOpcode(this: *WebSocket, message: []const u8, opcode: u32, compress: bool) SendStatus { + // return uws_ws_send_first_fragment_with_opcode(ssl_flag, this.raw(), message: [*c]const u8, length: usize, opcode: Opcode, compress: bool); + // } + pub fn sendLastFragment(this: *WebSocket, message: []const u8, compress: bool) SendStatus { + return uws_ws_send_last_fragment(ssl_flag, this.raw(), message.ptr, message.len, compress); + } + pub fn end(this: *WebSocket, code: i32, message: []const u8) void { + return uws_ws_end(ssl_flag, this.raw(), code, message.ptr, message.len); + } + pub fn cork(this: *WebSocket, ctx: anytype, comptime callback: anytype) void { + const ContextType = @TypeOf(ctx); + const Wrapper = struct { + pub fn wrap(user_data: ?*anyopaque) callconv(.C) void { + @call(.{ .modifier = .always_inline }, callback, .{bun.cast(ContextType, user_data.?)}); + } + }; - pub fn getRemoteAddressAsText(this: *WebSocket, buf: []u8) []u8 { - return buf[0..uws_ws_get_remote_address_as_text(ssl_flag, this.raw(), &buf.ptr)]; - } - }; + return uws_ws_cork(ssl_flag, this.raw(), Wrapper.wrap, ctx); + } + pub fn subscribe(this: *WebSocket, topic: []const u8) bool { + return uws_ws_subscribe(ssl_flag, this.raw(), topic.ptr, topic.len); + } + pub fn unsubscribe(this: *WebSocket, topic: []const u8) bool { + return uws_ws_unsubscribe(ssl_flag, this.raw(), topic.ptr, topic.len); + } + pub fn isSubscribed(this: *WebSocket, topic: []const u8) bool { + return uws_ws_is_subscribed(ssl_flag, this.raw(), topic.ptr, topic.len); + } + // pub fn iterateTopics(this: *WebSocket) { + // return uws_ws_iterate_topics(ssl_flag, this.raw(), callback: ?fn ([*c]const u8, usize, ?*anyopaque) callconv(.C) void, user_data: ?*anyopaque) void; + // } + pub fn publish(this: *WebSocket, topic: []const u8, message: []const u8) bool { + return uws_ws_publish(ssl_flag, this.raw(), topic.ptr, topic.len, message.ptr, message.len); + } + pub fn publishWithOptions(this: *WebSocket, topic: []const u8, message: []const u8, opcode: Opcode, compress: bool) bool { + return uws_ws_publish_with_options(ssl_flag, this.raw(), topic.ptr, topic.len, message.ptr, message.len, opcode, compress); + } + pub fn getBufferedAmount(this: *WebSocket) u32 { + return uws_ws_get_buffered_amount(ssl_flag, this.raw()); + } + pub fn getRemoteAddress(this: *WebSocket, buf: []u8) []u8 { + return buf[0..uws_ws_get_remote_address(ssl_flag, this.raw(), &buf.ptr)]; + } + + pub fn getRemoteAddressAsText(this: *WebSocket, buf: []u8) []u8 { + var copy = buf; + return buf[0..uws_ws_get_remote_address_as_text(ssl_flag, this.raw(), ©.ptr)]; + } }; }; } @@ -1261,22 +1487,22 @@ extern fn uws_app_listen_with_config( ) void; extern fn uws_constructor_failed(ssl: i32, app: *uws_app_t) bool; extern fn uws_num_subscribers(ssl: i32, app: *uws_app_t, topic: [*c]const u8) c_uint; -extern fn uws_publish(ssl: i32, app: *uws_app_t, topic: [*c]const u8, topic_length: usize, message: [*c]const u8, message_length: usize, opcode: uws_opcode_t, compress: bool) bool; +extern fn uws_publish(ssl: i32, app: *uws_app_t, topic: [*c]const u8, topic_length: usize, message: [*c]const u8, message_length: usize, opcode: Opcode, compress: bool) bool; extern fn uws_get_native_handle(ssl: i32, app: *uws_app_t) ?*anyopaque; extern fn uws_remove_server_name(ssl: i32, app: *uws_app_t, hostname_pattern: [*c]const u8) void; extern fn uws_add_server_name(ssl: i32, app: *uws_app_t, hostname_pattern: [*c]const u8) void; extern fn uws_add_server_name_with_options(ssl: i32, app: *uws_app_t, hostname_pattern: [*c]const u8, options: us_socket_context_options_t) void; extern fn uws_missing_server_name(ssl: i32, app: *uws_app_t, handler: uws_missing_server_handler, user_data: ?*anyopaque) void; extern fn uws_filter(ssl: i32, app: *uws_app_t, handler: uws_filter_handler, user_data: ?*anyopaque) void; -extern fn uws_ws(ssl: i32, app: *uws_app_t, pattern: [*c]const u8, behavior: uws_socket_behavior_t) void; +extern fn uws_ws(ssl: i32, app: *uws_app_t, ctx: *anyopaque, pattern: [*]const u8, pattern_len: usize, id: usize, behavior: *const WebSocketBehavior) void; extern fn uws_ws_get_user_data(ssl: i32, ws: ?*RawWebSocket) ?*anyopaque; extern fn uws_ws_close(ssl: i32, ws: ?*RawWebSocket) void; -extern fn uws_ws_send(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, opcode: uws_opcode_t) SendStatus; -extern fn uws_ws_send_with_options(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, opcode: uws_opcode_t, compress: bool, fin: bool) SendStatus; +extern fn uws_ws_send(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, opcode: Opcode) SendStatus; +extern fn uws_ws_send_with_options(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, opcode: Opcode, compress: bool, fin: bool) SendStatus; extern fn uws_ws_send_fragment(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, compress: bool) SendStatus; extern fn uws_ws_send_first_fragment(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, compress: bool) SendStatus; -extern fn uws_ws_send_first_fragment_with_opcode(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, opcode: uws_opcode_t, compress: bool) SendStatus; +extern fn uws_ws_send_first_fragment_with_opcode(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, opcode: Opcode, compress: bool) SendStatus; extern fn uws_ws_send_last_fragment(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, compress: bool) SendStatus; extern fn uws_ws_end(ssl: i32, ws: ?*RawWebSocket, code: i32, message: [*c]const u8, length: usize) void; extern fn uws_ws_cork(ssl: i32, ws: ?*RawWebSocket, handler: ?fn (?*anyopaque) callconv(.C) void, user_data: ?*anyopaque) void; @@ -1285,10 +1511,10 @@ extern fn uws_ws_unsubscribe(ssl: i32, ws: ?*RawWebSocket, topic: [*c]const u8, extern fn uws_ws_is_subscribed(ssl: i32, ws: ?*RawWebSocket, topic: [*c]const u8, length: usize) bool; extern fn uws_ws_iterate_topics(ssl: i32, ws: ?*RawWebSocket, callback: ?fn ([*c]const u8, usize, ?*anyopaque) callconv(.C) void, user_data: ?*anyopaque) void; extern fn uws_ws_publish(ssl: i32, ws: ?*RawWebSocket, topic: [*c]const u8, topic_length: usize, message: [*c]const u8, message_length: usize) bool; -extern fn uws_ws_publish_with_options(ssl: i32, ws: ?*RawWebSocket, topic: [*c]const u8, topic_length: usize, message: [*c]const u8, message_length: usize, opcode: uws_opcode_t, compress: bool) bool; +extern fn uws_ws_publish_with_options(ssl: i32, ws: ?*RawWebSocket, topic: [*c]const u8, topic_length: usize, message: [*c]const u8, message_length: usize, opcode: Opcode, compress: bool) bool; extern fn uws_ws_get_buffered_amount(ssl: i32, ws: ?*RawWebSocket) c_uint; -extern fn uws_ws_get_remote_address(ssl: i32, ws: ?*RawWebSocket, dest: [*c][*c]const u8) usize; -extern fn uws_ws_get_remote_address_as_text(ssl: i32, ws: ?*RawWebSocket, dest: [*c][*c]const u8) usize; +extern fn uws_ws_get_remote_address(ssl: i32, ws: ?*RawWebSocket, dest: *[*]u8) usize; +extern fn uws_ws_get_remote_address_as_text(ssl: i32, ws: ?*RawWebSocket, dest: *[*]u8) usize; const uws_res = opaque {}; extern fn uws_res_uncork(ssl: i32, res: *uws_res) void; extern fn uws_res_end(ssl: i32, res: *uws_res, data: [*c]const u8, length: usize, close_connection: bool) void; @@ -1361,14 +1587,23 @@ pub const DEDICATED_COMPRESSOR_64KB: i32 = 214; pub const DEDICATED_COMPRESSOR_128KB: i32 = 231; pub const DEDICATED_COMPRESSOR_256KB: i32 = 248; pub const DEDICATED_COMPRESSOR: i32 = 248; -pub const uws_compress_options_t = c_uint; +pub const uws_compress_options_t = i32; pub const CONTINUATION: i32 = 0; pub const TEXT: i32 = 1; pub const BINARY: i32 = 2; pub const CLOSE: i32 = 8; pub const PING: i32 = 9; pub const PONG: i32 = 10; -pub const uws_opcode_t = c_uint; + +pub const Opcode = enum(i32) { + text = 1, + binary = 2, + close = 8, + ping = 9, + pong = 10, + _, +}; + pub const SendStatus = enum(c_uint) { backpressure = 0, success = 1, diff --git a/src/jsc.zig b/src/jsc.zig index f4adbcc3a..df0bbec6c 100644 --- a/src/jsc.zig +++ b/src/jsc.zig @@ -26,6 +26,7 @@ pub const Jest = @import("./bun.js/test/jest.zig"); pub const API = struct { pub const Transpiler = @import("./bun.js/api/transpiler.zig"); pub const Server = @import("./bun.js/api/server.zig").Server; + pub const ServerWebSocket = @import("./bun.js/api/server.zig").ServerWebSocket; pub const SSLServer = @import("./bun.js/api/server.zig").SSLServer; pub const DebugServer = @import("./bun.js/api/server.zig").DebugServer; pub const DebugSSLServer = @import("./bun.js/api/server.zig").DebugSSLServer; |