Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow server-side components to send messages to client-side components #975

Open
rmorshea opened this issue May 11, 2023 · 4 comments · May be fixed by #1084
Open

Allow server-side components to send messages to client-side components #975

rmorshea opened this issue May 11, 2023 · 4 comments · May be fixed by #1084
Labels
priority-1-high Should be resolved ASAP. release-minor Warrents a minor release

Comments

@rmorshea
Copy link
Collaborator

rmorshea commented May 11, 2023

Current Situation

Presently, custom client-side components are able to send events back to the server. However, it is not possible to send events from server-side components to client-side ones.

Proposed Actions

Now that custom JS components have the ability to register callbacks with the client this should be technologically feasible. With that said, while users can listen in on particular message types, there is no concept of a message "target". We could achieve this by having message types of the form server-event:<the-target>, but this seems like a bit of a hack. Perhaps we can allow (require?) a nullable target field for this purpose.

Before diving into all those details though, we need to work out exactly what this interface should look like.

I can imagine having an interface similar to:

@component
def example():
    channel = use_channel()

    @use_effect
    async def delayed_message():
        import asyncio
        await asyncio.sleep(5)
        await channel.send({"my": "message"})
        response = await channel.receive()

    return custom_js_component({"channel_id": channel.id})

where custom_js_component would then subscribe to messages of the type channel-message and target channel.id.

@rmorshea rmorshea added the flag-triage Not prioritized. label May 11, 2023
@Archmonger
Copy link
Contributor

Tangentially related

@rmorshea rmorshea changed the title Allow Server-side components to send events to client-side components Allow server-side components to send messages to client-side components May 11, 2023
@Archmonger
Copy link
Contributor

Archmonger commented May 11, 2023

The name use_channel kind of implies that multiple channels and/or channel groups can be defined per component. Here's some other potential names

  • use_data_message
  • use_message
  • use_messenger
  • use_message_listener
  • use_message_signal
  • use_message_subscriber
  • use_signal_listener
  • use_consumer
  • use_data_channel
  • use_data_messenger
  • use_data_transport
  • use_data_dispatcher
  • use_communicator

The design below would allow for an event-driven architecture when possible, inspired by channels.

With any design we make, we're going to need to be careful about our asyncio cancellation policy for messengers. This will extend to us thinking deeper about async use_effect.

from dataclasses import dataclass

from reactpy import component, hooks


@dataclass
class DataMessenger:
    def __init__(self, *args, **kwargs):
        self.args = args or ()
        self.kwargs = kwargs or {}

    async def send(self, message):
        """This method typically isn't overridden."""
        ...

    async def recieve(self, message):
        """This method typically gets overriden with custom user behavior."""
        ...

    async def register(self, ... ):
        """This method typically isn't overridden. Used to register a message handler to a component."""
        ...


def use_messenger(messenger: DataMessenger) -> DataMessenger:
    """This is a hook that allows users to communicate to client-side components.
    The given `messenger` will be registered to the current component.
    """
    MESSENGER_CONTEXT: list = hooks.use_context(...)
    
    # A given `Messenger` should persist throughout the entire lifecycle of a component
    if messenger not in MESSENGER_CONTEXT:  # Not a usable implementation, but you get the point
        messenger.register(...)

    # Update `args`/`kwargs` on each component re-render.
    else:
        messenger.args = MESSENGER_CONTEXT[0].args
        messenger.kwargs = MESSENGER_CONTEXT[0].kwargs

    # Give the user access to the registered messenger, if he wants to do some shenanigans within `use_effect`.
    return messenger


@component
def example():
    # If the user only wants to be event driven, then he won't need to save a `messenger = ...` variable
    messenger = use_messenger(DataMessenger(example_value=1))

    @hooks.use_effect
    async def send_message():
        # Using `messenger` methods here would rely on our half-baked async `use_effect`
        # I think this might be an argument to fleshing out async effects
        await messenger.send("Hello, World!")

@rmorshea
Copy link
Collaborator Author

The name use_channel kind of implies that multiple channels and/or channel groups can be defined per component

I think this is true. One could imagine a server-side component that constructs several channels in order to communicate with different client-side components. use_messenger would be fine too though.

@Archmonger
Copy link
Contributor

Archmonger commented May 11, 2023

If we're allowing support for multiple communicators, imo that narrows it down to the following:

  • use_channel
  • use_data_channel
  • use_messenger
  • use_data_messenger
  • use_communicator

I updated the example above to better outline how persistence of DataMessenger can be achieved.

@Archmonger Archmonger added type-feature release-minor Warrents a minor release and removed flag-triage Not prioritized. labels May 15, 2023
@Archmonger Archmonger linked a pull request Feb 23, 2024 that will close this issue
2 tasks
@Archmonger Archmonger added the priority-1-high Should be resolved ASAP. label Feb 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
priority-1-high Should be resolved ASAP. release-minor Warrents a minor release
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants