Skip to content

Conversation

@4e6
Copy link
Contributor

@4e6 4e6 commented Dec 8, 2025

Pull Request Description

Important Notes

Checklist

Please ensure that the following checklist has been satisfied before submitting the PR:

  • The documentation has been updated, if necessary.
  • Screenshots/screencasts have been attached, if there are any visual changes. For interactive or animated visual changes, a screencast is preferred.
  • All code follows the
    Scala,
    Java,
    TypeScript,
    and
    Rust
    style guides. In case you are using a language not listed above, follow the Rust style guide.
  • Unit tests have been written where possible.
  • If meaningful changes were made to logic or tests affecting Enso Cloud integration in the libraries,
    or the Snowflake database integration, a run of the Extra Tests has been scheduled.
    • If applicable, it is suggested to paste a link to a successful run of the Extra Tests.

@4e6 4e6 self-assigned this Dec 8, 2025
@4e6 4e6 added the CI: No changelog needed Do not require a changelog entry for this PR. label Dec 8, 2025
@4e6 4e6 force-pushed the wip/db/14142-ydoc-websocket branch 2 times, most recently from 7356274 to 4371781 Compare December 8, 2025 18:03
@@ -0,0 +1,3 @@
module org.enso.ydoc.api {
exports org.enso.ydoc.api;
Copy link
Member

@JaroslavTulach JaroslavTulach Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • There is no need for a new API module.
  • there already is YdocServerApi
  • please put the new interfaces there as inner classes of YdocServerApi.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Possibly you can move YdocServerApi to the new org.enso.ydoc.api module.
  • but the whole API needs to be co-located

package org.enso.ydoc.api;

public class NoOpMessageCallbacks implements MessageCallbacks {
public static final NoOpMessageCallbacks INSTANCE = new NoOpMessageCallbacks();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Please don't put NoOpMessagesCallbacks implementation class into the API
  • remove it
  • if you have to have it, then encapsulate it
  • put public static final MessageCallbacks NO_OP into MessageCallbacks and make the implementation class package private

Copy link
Member

@JaroslavTulach JaroslavTulach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ideally HostAccess is configured internally
  • and both tests and production code just work with the default configuration
  • possible trick to achieve that is to crate a proxy around interfaces passed to JavaScript

return this;
}

public Builder hostAccessBuilder(HostAccess.Builder hostAccessBuilder) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Why is this a builder?
  • Isn't just HostAccess instance good enough?
Suggested change
public Builder hostAccessBuilder(HostAccess.Builder hostAccessBuilder) {
public Builder hostAccess(HostAccess hostAccessBuilder) {
  • without WebEnvironment.defaultHostAccess being public, one might need a builder
  • but having both builder and WebEnvironment.defaultHostAccess seems unnecessary

WebEnvironment.defaultHostAccess
// allowImplementations is required to call methods on JS objects from Java, i.e. to
// call methods on `YjsChannel` object returned from JS
.allowImplementations(YjsChannel.class)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • allowing implementation of a well known interface is OK
  • but opening up allowAccess to a random callbacks.getClass() class is a security flaw
  • please create a proxy around callbacks and expose its methods
    • preferrably via @HostAccess.Explicit
    • or via allowAccess(ProxyMessageCallbacks.getDeclararedMethod("")) but while hardcoding ProxyMessageCallbacks class

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current state with:

.allowImplementations(YjsChannel.class)
.allowPublicAccess(true);

is certainly better than the original one. Thanks.

Still, it be good to remove .allowPublicAccess(true) and use @HostAccess.Explicit annotation instead.

@@ -0,0 +1,3 @@
module org.enso.ydoc.api {
exports org.enso.ydoc.api;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Possibly you can move YdocServerApi to the new org.enso.ydoc.api module.
  • but the whole API needs to be co-located

@4e6 4e6 force-pushed the wip/db/14142-ydoc-websocket branch from 71e9b86 to 8713e6f Compare December 13, 2025 16:18
for (const item of items) {
// Only notify handlers if the message is from another sender
if (item.senderId !== this.senderId) {
this.notifyHandlers(item.payload)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • As of 8713e6f we are probably dealing with ever growing Y.Array
  • somehow we need to find out that the "message has been delivered" to all handlers
    • that's kind of hard in a distributed framework
    • where handlers can appear and diappear
  • one solution would be to add receiverId to item
    • deliver the message only when senderId matches receiverId
    • e.g. if (item.receiverId === this.senderId)`
    • if there was only one receiver of a message
    • then it'd be the receiver's responsibility to remove the item from array

@4e6 4e6 force-pushed the wip/db/14142-ydoc-websocket branch from 486cb1d to c1ee0ea Compare December 22, 2025 19:58
@4e6 4e6 force-pushed the wip/db/14142-ydoc-websocket branch from f122b95 to 9625b1f Compare December 29, 2025 11:49
Copy link
Member

@JaroslavTulach JaroslavTulach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It all looks quite decent. Few comments inline.

/**
* A bidirectional communication channel backed by Y.Array.
*
* This class allows multiple parties to send and receive messages through a shared
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • really "multiple parties"?
  • shouldn't the YjsChannel be just bidirectional?


// Polyfill DOM event types for Node.js environment
if (typeof globalThis.CloseEvent === 'undefined') {
;(globalThis as any).CloseEvent = class CloseEvent extends Event {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line prefixed by ;?

Suggested change
;(globalThis as any).CloseEvent = class CloseEvent extends Event {
(globalThis as any).CloseEvent = class CloseEvent extends Event {

import java.util.ServiceLoader;
import org.enso.ydoc.api.MessageCallbacks;

public abstract class YdocServerApi {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we put YdocServerApi into org.enso.ydoc.api module as well? As this comment suggests?

Comment on lines +28 to +29
@Override
public void onConnect(YjsChannel channel) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@Override
public void onConnect(YjsChannel channel) {
@Override
@HostAccess.Export
public void onConnect(YjsChannel channel) {

// public access is required to recognize Java lambdas passed to
// `YjsChannel::subscribe` method as JS functions.
.allowPublicAccess(true)
.allowAccess(TestCallbacks.class.getDeclaredMethod("onConnect", YjsChannel.class))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.allowAccess(TestCallbacks.class.getDeclaredMethod("onConnect", YjsChannel.class))
// no need for TestCallbacks registration

rather use @HostAccess.Explicit


@Test
public void onConnectSend() throws Exception {
var res = new AtomicReference<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • I'd slightly prefer you to create CountingTest class and register its instance instead
  • annotate its set method with @HostAccess.Explicit

"javax.management.**;java.lang.**;java.rmi.**;javax.security.auth.Subject;!*");

if (args.length == 2) {
var then = System.currentTimeMillis();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Is this public static void main still useful for anything?
  • Shouldn't it be removed?

WebEnvironment.defaultHostAccess
// allowImplementations is required to call methods on JS objects from Java, i.e. to
// call methods on `YjsChannel` object returned from JS
.allowImplementations(YjsChannel.class)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current state with:

.allowImplementations(YjsChannel.class)
.allowPublicAccess(true);

is certainly better than the original one. Thanks.

Still, it be good to remove .allowPublicAccess(true) and use @HostAccess.Explicit annotation instead.

var bindings = ctx.getBindings("js");
bindings.putMember("YDOC_HOST", hostname);
bindings.putMember("YDOC_PORT", port);
bindings.putMember("YDOC_MESSAGE_CALLBACKS", callbacks);
Copy link
Member

@JaroslavTulach JaroslavTulach Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • registering non-primitive type as global symbol seems strange
  • with YDOC_HOST one could post it as environment variable, but
  • one cannot register callbacks via environment variable
  • why don't create a initializeYdocServer function
  • and call it here directly with proper arguments?


configureAllDebugLogs(debug)

if (YDOC_MESSAGE_CALLBACKS == undefined) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • this check makes main.ts unusable from command line
    • at least I believe usage from CLI was the motivation for these YDOC_ (environment) variables
  • why don't we define:
function initializeYdocServer(YDOC_HOST, YDOC_PORT, YDOC_LS_DEBUG, YDOC_MESSAGE_CALLBACKS) {
  • and then just call this method with proper arguments from Java?

@4e6 4e6 force-pushed the wip/db/14142-ydoc-websocket branch from 27df1af to b5f7639 Compare January 1, 2026 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI: No changelog needed Do not require a changelog entry for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Avoid networking layer of Akka by ydoc-server talking directly to the engine+ls

3 participants