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

Bonito subsessions can recieve updates before they are initalised #260

Open
frankier opened this issue Oct 19, 2024 · 2 comments
Open

Bonito subsessions can recieve updates before they are initalised #260

frankier opened this issue Oct 19, 2024 · 2 comments

Comments

@frankier
Copy link
Contributor

When using a subsession, if there is an observable update from the server to the client before the subsession is initialized, it will be sent before the subsession is initialized.

Here are two examples. Jupyter:

{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "806de17d",
   "metadata": {},
   "outputs": [],
   "source": [
    "using Bonito"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7b076fc5",
   "metadata": {},
   "outputs": [],
   "source": [
    "App() do\n",
    "    obs::Observable{String} = Observable(\"not updated\")\n",
    "    @async begin\n",
    "        obs[] = \"updated!\"\n",
    "    end\n",
    "    DOM.div(obs)\n",
    "end"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "35002599",
   "metadata": {},
   "outputs": [],
   "source": [
    "App() do\n",
    "    obs::Observable{String} = Observable(\"not updated\")\n",
    "    @async begin\n",
    "        obs[] = \"updated!\"\n",
    "    end\n",
    "    DOM.div(obs)\n",
    "end"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6126a28c",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Julia Bonito update 1.10.5",
   "language": "julia",
   "name": "julia-bonito-update-1.10"
  },
  "language_info": {
   "file_extension": ".jl",
   "mimetype": "application/julia",
   "name": "julia",
   "version": "1.10.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}

Externally served using HTTP.jl using #254:

using HTTP
using Bonito: App, Observable, show_html
using Bonito.DOM


function html(app; kwargs...)
    io = IOBuffer()
    show_html(io, app; kwargs...)
    take!(io)
end

function main()
    # start a blocking server
    session_root_storage = Dict()
    HTTP.listen() do http::HTTP.Stream
        HTTP.setstatus(http, 200)
        HTTP.startwrite(http)
        req_uri = HTTP.URI(http.message.target)
        if req_uri.path == "/fragment"
            params = HTTP.queryparams(req_uri)
            sid = params["sid"]
            session = session_root_storage[params["sid"]]
            app = App() do
                obs::Observable{String} = Observable("not updated")
                @async begin
                    obs[] = "updated!"
                end
                DOM.div(obs)
            end
            app_html = html(app; parent=session)
            write(http, app_html)
        else
            root_app = App(_ -> DOM.div(""))
            app_html = html(root_app)
            session = root_app.session[]
            session_root_storage[session.id] = session
            write(http, "<html><body>")
            write(http, "<script src=\"https://unpkg.com/[email protected]\"></script>")
            write(http, "<h1>fragment insertion</h1>")
            write(http, "<button hx-get=\"/fragment?sid=$(session.id)\" hx-target=\"#results\">Click Me</button>")
            write(http, "<div id=\"results\">")
            write(http, app_html)
            write(http, "</div>")
            write(http, "</body></html>")
        end
    end
end

if abspath(PROGRAM_FILE) == @__FILE__
    main()
end

In both cases the subsession will cause an error like this:

Error while processing message {"payload":"updated!","id":"8","msg_type":"0"}
send_error @ 1d929f6cee42ea5c6b10f3e2d4998b5383ad74d4-Bonito.bundled.js:3490
process_message @ 1d929f6cee42ea5c6b10f3e2d4998b5383ad74d4-Bonito.bundled.js:3669
(anonymous) @ f3e6095cd6229b6445b3ef8b9820ee76fe9f79c7-Websocket.bundled.js:95
(anonymous) @ 1d929f6cee42ea5c6b10f3e2d4998b5383ad74d4-Bonito.bundled.js:3636
lock @ 1d929f6cee42ea5c6b10f3e2d4998b5383ad74d4-Bonito.bundled.js:3630
(anonymous) @ f3e6095cd6229b6445b3ef8b9820ee76fe9f79c7-Websocket.bundled.js:94
websocket.onmessage @ f3e6095cd6229b6445b3ef8b9820ee76fe9f79c7-Websocket.bundled.js:89
1d929f6cee42ea5c6b10f3e2d4998b5383ad74d4-Bonito.bundled.js:3491 Error: Key 8 not found! undefined
    at lookup_global_object (1d929f6cee42ea5c6b10f3e2d4998b5383ad74d4-Bonito.bundled.js:3706:11)
    at Object.process_message (1d929f6cee42ea5c6b10f3e2d4998b5383ad74d4-Bonito.bundled.js:3648:17)
    at f3e6095cd6229b6445b3ef8b9820ee76fe9f79c7-Websocket.bundled.js:95:32
    at 1d929f6cee42ea5c6b10f3e2d4998b5383ad74d4-Bonito.bundled.js:3636:33
    at new Promise (<anonymous>)
    at Lock.lock (1d929f6cee42ea5c6b10f3e2d4998b5383ad74d4-Bonito.bundled.js:3630:16)
    at f3e6095cd6229b6445b3ef8b9820ee76fe9f79c7-Websocket.bundled.js:94:48
    at new Promise (<anonymous>)
    at websocket.onmessage (f3e6095cd6229b6445b3ef8b9820ee76fe9f79c7-Websocket.bundled.js:89:17)
send_error @ 1d929f6cee42ea5c6b10f3e2d4998b5383ad74d4-Bonito.bundled.js:3491
process_message @ 1d929f6cee42ea5c6b10f3e2d4998b5383ad74d4-Bonito.bundled.js:3669
(anonymous) @ f3e6095cd6229b6445b3ef8b9820ee76fe9f79c7-Websocket.bundled.js:95
(anonymous) @ 1d929f6cee42ea5c6b10f3e2d4998b5383ad74d4-Bonito.bundled.js:3636
lock @ 1d929f6cee42ea5c6b10f3e2d4998b5383ad74d4-Bonito.bundled.js:3630
(anonymous) @ f3e6095cd6229b6445b3ef8b9820ee76fe9f79c7-Websocket.bundled.js:94
websocket.onmessage @ f3e6095cd6229b6445b3ef8b9820ee76fe9f79c7-Websocket.bundled.js:89
@frankier
Copy link
Contributor Author

I think at least part of the problem is that each JSUpdateObservable is attached to the root session:

function register_observable!(session::Session, obs::Observable)
# Always register with root session!
# TODO, this may be a problem for Observable{Observable}
# since the updates are serialized via the root session then, which means they will never get freed
root = root_session(session)
# Only register one time
if !haskey(root.session_objects, obs.id)
updater = JSUpdateObservable(root, obs.id)
# Don't deregister on root / or session close
# The updaters callbacks are freed manually in delete_cached!`
on(updater, obs)
end
return
end

I guess there's a reason for this. It seems like if they were attached to the subsession it would be easier to queue updates until the subsession is initalised.

@SimonDanisch
Copy link
Owner

Ah, damn!
Yeah, observables need to be registered with the root session, since they're globally unique and if each session would register the same observable independently, they'd send douple updates to JS.
Have to figure out how to fix this correctly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants