Long running uploads via external uploaders cause problems when reusing websocket connections
Environment
- Elixir version: 1.16.2
- Phoenix version: 1.7.14
- Phoenix LiveView version: 1.0.0-rc.6
- Operating system: MacOS 14.5 (arm64)
- Browsers you attempted to reproduce this bug on: Firefox 129.0b9, Chrome 127.0.6533.74
- Does the problem persist after removing "assets/node_modules" and trying again? Yes
Actual behavior
- Start a long-running upload.
- Remount the current LiveView during upload (clicking "same, but different" in my example).
This should create a new LiveView process that reuses the existing websocket connection. The new LiveView again sets up uploads.
However, on the frontend, the JavaScript happily keeps uploading and sending messages to the over the existing websocket connection, to the new LiveView process. This new LiveView process doesn't have the old upload state, thus crashes like this:
** (KeyError) key "phx-F-e1tog74Q2dEAgB" not found in: %{"phx-F-e1wlp0SMNl9giB" => :videos}
:erlang.map_get("phx-F-e1tog74Q2dEAgB", %{"phx-F-e1wlp0SMNl9giB" => :videos})
(phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/upload.ex:196: Phoenix.LiveView.Upload.get_upload_by_ref!/2
(phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/upload.ex:126: Phoenix.LiveView.Upload.update_progress/4
(phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/channel.ex:176: anonymous fn/4 in Phoenix.LiveView.Channel.handle_info/2
(phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/channel.ex:1419: Phoenix.LiveView.Channel.write_socket/4
(phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/channel.ex:174: Phoenix.LiveView.Channel.handle_info/2
A similar thing happens when navigating to another LiveView:
- Start a long-running upload.
- Navigate away (clicking "another page" in my example).
In my example, the new LiveView process didn't call allow_uploads, and thus doesn't have an uploading state, raising this error when the JavaScript happily reports upload progress:
** (ArgumentError) no uploads have been allowed on LiveView named ReproductionWeb.OtherLive
(phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/upload.ex:195: Phoenix.LiveView.Upload.get_upload_by_ref!/2
(phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/upload.ex:126: Phoenix.LiveView.Upload.update_progress/4
(phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/channel.ex:176: anonymous fn/4 in Phoenix.LiveView.Channel.handle_info/2
(phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/channel.ex:1419: Phoenix.LiveView.Channel.write_socket/4
(phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/channel.ex:174: Phoenix.LiveView.Channel.handle_info/2
Expected behavior
I would expect running uploads to either:
- Terminate when navigating away.
- Block the page (show a warning like "are you sure you want to leave this page, there are unsaved changes", see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event).
Update: it only happens with JS-based external uploaders. Phoenix.LiveView.UploadWriters are terminated correctly when navigating. (See RobinBoers/lv-long-running-uploads#upload-writers).
I think this issue is related to #3287. Seems like the proposed solution there (adding a cancel callback to the JS uploader), would also work in our case.
hacked something just to appease the errors:
window.addEventListener("phx:page-loading-start", e => {
let el = document.getElementById("confirm-unsaved-upload")
if (el && el.hasAttribute("data-uploading") && el.getAttribute("data-uploading") === "true") {
if (confirm("Changes you made may not be saved")) {
activeUploads.forEach(entry => {
console.log(entry)
entry.abort()
})
} else {
// window.location.assign = e.state?.previousUrl || '/'
// window.location.replace(window.location.href)
history.pushState(null, null, window.location.href) // This pushes the original URL back to the history, effectively "cancelling" the popstate.
}
}
topbar.show(300)
})
Though you have to add beforeunload if you want to ask the user when doing page reloads.
@RobinBoers yeah and #3287. Is there a workaround for this related ?websocket? issues? seems like doing lv-side of things is great until you're making external calls