@@ -57,6 +57,7 @@ WebSocket::WebSocket(
5757 binaryType_ (FeatureFlags::get(js).getWebsocketBinaryTypeDefault() ? BinaryType::BLOB
5858 : BinaryType::ARRAYBUFFER),
5959 serializedAttachment (kj::mv(package.serializedAttachment)),
60+ allowHalfOpen (package.allowHalfOpen),
6061 farNative (initNative(ioContext,
6162 ws,
6263 kj::mv (KJ_REQUIRE_NONNULL(package.maybeTags)),
@@ -76,6 +77,7 @@ WebSocket::WebSocket(jsg::Lock& js, kj::Own<kj::WebSocket> native)
7677 url(kj::none),
7778 binaryType_(FeatureFlags::get(js).getWebsocketBinaryTypeDefault() ? BinaryType::BLOB
7879 : BinaryType::ARRAYBUFFER),
80+ allowHalfOpen(!FeatureFlags::get(js).getWebSocketCloseReadyStateClosed()),
7981 farNative(nullptr ),
8082 outgoingMessages(IoContext::current().addObject(kj::heap<OutgoingMessagesMap>())) {
8183 auto nativeObj = kj::heap<Native>();
@@ -88,6 +90,7 @@ WebSocket::WebSocket(jsg::Lock& js, kj::String url)
8890 url(kj::mv(url)),
8991 binaryType_(FeatureFlags::get(js).getWebsocketBinaryTypeDefault() ? BinaryType::BLOB
9092 : BinaryType::ARRAYBUFFER),
93+ allowHalfOpen(!FeatureFlags::get(js).getWebSocketCloseReadyStateClosed()),
9194 farNative(nullptr ),
9295 outgoingMessages(IoContext::current().addObject(kj::heap<OutgoingMessagesMap>())) {
9396 auto nativeObj = kj::heap<Native>();
@@ -398,7 +401,7 @@ kj::Promise<DeferredProxy<void>> WebSocket::couple(
398401 co_return co_await promise;
399402}
400403
401- void WebSocket::accept (jsg::Lock& js) {
404+ void WebSocket::accept (jsg::Lock& js, jsg::Optional<AcceptOptions> options ) {
402405 auto & native = *farNative;
403406 JSG_REQUIRE (!native.state .is <AwaitingConnection>(), TypeError,
404407 " Websockets obtained from the 'new WebSocket()' constructor cannot call accept" );
@@ -414,6 +417,12 @@ void WebSocket::accept(jsg::Lock& js) {
414417 return ;
415418 }
416419
420+ KJ_IF_SOME (opts, options) {
421+ KJ_IF_SOME (value, opts.allowHalfOpen ) {
422+ allowHalfOpen = AllowHalfOpen (value);
423+ }
424+ }
425+
417426 internalAccept (js, IoContext::current ().getCriticalSection ());
418427}
419428
@@ -605,11 +614,8 @@ void WebSocket::send(jsg::Lock& js, kj::OneOf<kj::Array<byte>, kj::String> messa
605614 KJ_UNREACHABLE;
606615 }();
607616
608- auto pendingAutoResponses =
609- autoResponseStatus.pendingAutoResponseDeque .size () - autoResponseStatus.queuedAutoResponses ;
610- autoResponseStatus.queuedAutoResponses = autoResponseStatus.pendingAutoResponseDeque .size ();
611617 outgoingMessages->insert (
612- GatedMessage{kj::mv (maybeOutputLock), kj::mv (msg), pendingAutoResponses });
618+ GatedMessage{kj::mv (maybeOutputLock), kj::mv (msg), getPendingAutoResponseCount () });
613619
614620 ensurePumping (js);
615621}
@@ -680,22 +686,13 @@ void WebSocket::close(
680686
681687 assertNoError (js);
682688
683- // pendingAutoResponses stores the number of queuedAutoResponses that will be pumped before sending
684- // the current GatedMessage, guaranteeing order.
685- // queuedAutoResponses stores the total number of auto-response messages that are already in accounted
686- // for in previous GatedMessages. This is useful to easily calculate the number of pendingAutoResponses
687- // for each new GateMessage.
688- auto pendingAutoResponses =
689- autoResponseStatus.pendingAutoResponseDeque .size () - autoResponseStatus.queuedAutoResponses ;
690- autoResponseStatus.queuedAutoResponses = autoResponseStatus.pendingAutoResponseDeque .size ();
691-
692689 outgoingMessages->insert (GatedMessage{IoContext::current ().waitForOutputLocksIfNecessary (),
693690 kj::WebSocket::Close{
694691 // Code 1005 actually translates to sending a close message with no body on the wire.
695692 static_cast <uint16_t >(code.orDefault (1005 )),
696693 kj::mv (reason).orDefault (jsg::USVString (kj::str ())),
697694 },
698- pendingAutoResponses });
695+ getPendingAutoResponseCount () });
699696
700697 native.closedOutgoing = true ;
701698 closedOutgoingForHib = true ;
@@ -990,6 +987,13 @@ kj::Promise<void> WebSocket::pump(IoContext& context,
990987 completed = true ;
991988}
992989
990+ size_t WebSocket::getPendingAutoResponseCount () {
991+ auto count =
992+ autoResponseStatus.pendingAutoResponseDeque .size () - autoResponseStatus.queuedAutoResponses ;
993+ autoResponseStatus.queuedAutoResponses = autoResponseStatus.pendingAutoResponseDeque .size ();
994+ return count;
995+ }
996+
993997void WebSocket::tryReleaseNative (jsg::Lock& js) {
994998 // If the native WebSocket is no longer needed (the connection closed) and there are no more
995999 // messages to send, we can discard the underlying connection.
@@ -1057,6 +1061,23 @@ kj::Promise<kj::Maybe<kj::Exception>> WebSocket::readLoop(
10571061 }
10581062 KJ_CASE_ONEOF (close, kj::WebSocket::Close) {
10591063 native.closedIncoming = true ;
1064+ if (!allowHalfOpen.toBool () && !native.closedOutgoing && !native.outgoingAborted &&
1065+ !native.state .is <Released>()) {
1066+ // When allowHalfOpen is false (the spec-compliant default with the
1067+ // no_web_socket_half_open_by_default compat flag), automatically send a reciprocal
1068+ // Close frame through the outgoing message pump so that readyState is CLOSED (3)
1069+ // when the close event fires. Skip if a close frame was already sent (e.g. the
1070+ // application called close() before the server sent its Close), or if the outgoing
1071+ // side is otherwise unusable.
1072+ outgoingMessages->insert (
1073+ GatedMessage{IoContext::current ().waitForOutputLocksIfNecessary (),
1074+ kj::WebSocket::Close{close.code , kj::str (close.reason )},
1075+ getPendingAutoResponseCount ()});
1076+
1077+ native.closedOutgoing = true ;
1078+ closedOutgoingForHib = true ;
1079+ ensurePumping (js);
1080+ }
10601081 dispatchEventImpl (js, js.alloc <CloseEvent>(close.code , kj::mv (close.reason ), true ));
10611082 // Native WebSocket no longer needed; release.
10621083 tryReleaseNative (js);
@@ -1202,6 +1223,7 @@ WebSocket::HibernationPackage WebSocket::buildPackageForHibernation() {
12021223 .serializedAttachment = kj::mv (serializedAttachment),
12031224 .maybeTags = kj::none,
12041225 .closedOutgoingConnection = closedOutgoingForHib,
1226+ .allowHalfOpen = allowHalfOpen,
12051227 };
12061228}
12071229
0 commit comments