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

Use Socket.IO for message transport #449

Merged
merged 11 commits into from Feb 6, 2023
Merged

Conversation

thavocado
Copy link
Contributor

@thavocado thavocado commented Feb 5, 2023

All Submissions:

  • Have you followed the guidelines stated in CONTRIBUTING.md file?
  • Have you checked to ensure there aren't any other open Pull Requests for the desired changed?

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

New Feature Submission:

  • Does your submission pass the tests?
  • Have you linted your code locally prior to submission?

Description

A rework of the websocket implementation in Pynecone.

Benefits

  1. Socket.IO is a modern socket library used by major companies
  2. Starts connections using long polling (http/https) and upgrades to websocket as a fallback if websockets are blocked for some reason
  3. It fixes the issues with Safari iOS and mobile devices (more testing needed for edge cases), potentially closes Websockets connection drops on Safari/iOS #258
  4. Allows for seamless connections even when WiFi connection is lost and device switches to data
  5. Removes need for reconnections on focus fix, which closes Websocket was closed meanwhile asyncio tasks is running #424
  6. Works on Brave browser which closes Brave browser websocket compatibility issue with Pynecone  #423
  7. It is now theoretically possible to make websockets optional as a config option, as in Make websockets optional #287, without separate code paths
  8. Because this PR uses AsyncServer, it will be easier to implement implement server side / async events in the future
  9. It is easy to create events which are emitted to all users, or groups of users ("rooms"), making it possible to build chat apps or even a Discord clone in Pynecone in the future
  10. Requires no change in server setup to deploy, the default /socket.io endpoint has been replaced with the usual /event endpoint

I've tested this PR locally and on a production server.

@Alek99 Alek99 self-requested a review February 5, 2023 21:11
Copy link
Contributor

@picklelo picklelo left a comment

Choose a reason for hiding this comment

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

Awesome job!! Tested locally and it worked nicely - just a couple comments

@@ -85,24 +100,6 @@ def __call__(self) -> FastAPI:
"""
return self.api

def add_default_endpoints(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

We should keep the ping endpoint

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added it back in. For future reference it can be tested like so:

    socket.current.emit('ping')
    console.log('ping')
    socket.current.on('ping', function(data) {
      console.log(data);
    });

In the console you should see:

ping
pong

# To make state changes.
self.api.websocket(str(constants.Endpoint.EVENT))(event(app=self))

def add_cors(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we not need this anymore?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's taken care of when the AsyncServer is created on the App __init__ :

    self.sio = AsyncServer(async_mode="asgi", cors_allowed_origins="*")

There's also cors_credentials= which defaults to True already.

python-scoketio does not have CORS options like allow_methods and allow_headers but I believe it's configured to avoid cross-origin problems with browsers by allowing all headers and methods anyway.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay got it nice

@@ -327,52 +324,6 @@ def compile(self, force_compile: bool = False):
compiler.compile_components(custom_components)


async def ping() -> str:
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be useful for health checks, etc.

pynecone/app.py Outdated
self.sio = AsyncServer(async_mode="asgi", cors_allowed_origins="*")

# Create the socket app. Note 'event' replaces the default 'socket.io' path.
socket_app = ASGIApp(self.sio, socketio_path="event")
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use constants.Endpoint.EVENT instead of a string here (and in the EventNameSpace below)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll do that. Note that str(constants.Endpoint.EVENT) returns "/event" not "event" so I'll also update state.js accordingly, since those have to match.

Copy link
Contributor Author

@thavocado thavocado Feb 6, 2023

Choose a reason for hiding this comment

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

Actually that might cause a bunch of confusion in the future, for example you call ping with 'ping' but it returns on '\ping'. So I'll modify constants.py `Endpoint' to allow it to return the literal value without the leading ''.

Copy link
Contributor Author

@thavocado thavocado Feb 6, 2023

Choose a reason for hiding this comment

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

Now that I think about it it's still confusing. There's potential confusion between the URL endpoint /event (a namespace) and the socket event event (they're separate concepts in socket.io). I'll separate those in constants.py to avoid future problems.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good!

@picklelo
Copy link
Contributor

picklelo commented Feb 5, 2023

Also, can you update the pynecone/.templates/web/pcversion.txt file to v0.1.15 - then it will let people know they have to pc init again

@thavocado
Copy link
Contributor Author

Thanks for the feedback. Please see the latest commits and let me know if there's anything else!

@JeremyLG
Copy link

JeremyLG commented Feb 6, 2023

Magnifique, you're the boss @advo-kat
I can confirm it completely fixes the linked issue

You rock !

Copy link
Contributor

@picklelo picklelo left a comment

Choose a reason for hiding this comment

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

Sweet great job!

# To make state changes.
self.api.websocket(str(constants.Endpoint.EVENT))(event(app=self))

def add_cors(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

Okay got it nice

@picklelo picklelo merged commit 50a7c02 into reflex-dev:main Feb 6, 2023
ACucos1 pushed a commit to ACucos1/rd-pynecone that referenced this pull request Feb 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants