From 526c66b79a234c01a0152c0b728a93e9e2773430 Mon Sep 17 00:00:00 2001 From: Falko Schindler Date: Sun, 20 Oct 2024 05:46:37 +0200 Subject: [PATCH] Improve generation of unique tab IDs (#3876) This PR improves the way tab IDs are generated to avoid duplicated tabs causing shared tab storage (#3872). The ides originates from this post on StackOverflow: https://stackoverflow.com/a/36807854/3419103 I successfully tested it with this code snippet: ```py @ui.page('/') async def main(): await ui.context.client.connected() if 'id' not in app.storage.tab: app.storage.tab['id'] = str(uuid.uuid4()) ui.label(app.storage.tab['id']) ``` An automated pytest would be great. But I guess we can't properly simulate duplicating tabs. --- nicegui/air.py | 2 ++ nicegui/nicegui.py | 2 ++ nicegui/static/nicegui.js | 22 ++++++++++++++++------ nicegui/storage.py | 5 +++++ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/nicegui/air.py b/nicegui/air.py index 3ea6c6207..348e553d2 100644 --- a/nicegui/air.py +++ b/nicegui/air.py @@ -130,6 +130,8 @@ def _handle_handshake(data: Dict[str, Any]) -> bool: return False client = Client.instances[client_id] client.environ = data['environ'] + if data.get('old_tab_id'): + core.app.storage.copy_tab(data['old_tab_id'], data['tab_id']) client.tab_id = data['tab_id'] client.on_air = True client.handle_handshake() diff --git a/nicegui/nicegui.py b/nicegui/nicegui.py index 456027ebf..c08115465 100644 --- a/nicegui/nicegui.py +++ b/nicegui/nicegui.py @@ -167,6 +167,8 @@ async def _on_handshake(sid: str, data: Dict[str, str]) -> bool: client = Client.instances.get(data['client_id']) if not client: return False + if data.get('old_tab_id'): + app.storage.copy_tab(data['old_tab_id'], data['tab_id']) client.tab_id = data['tab_id'] if sid[:5].startswith('test-'): client.environ = {'asgi.scope': {'description': 'test client', 'type': 'test'}} diff --git a/nicegui/static/nicegui.js b/nicegui/static/nicegui.js index c525fce46..04f70b29c 100644 --- a/nicegui/static/nicegui.js +++ b/nicegui/static/nicegui.js @@ -294,6 +294,16 @@ function createRandomUUID() { } } +const OLD_TAB_ID = sessionStorage.__nicegui_tab_closed === "false" ? sessionStorage.__nicegui_tab_id : null; +const TAB_ID = + !sessionStorage.__nicegui_tab_id || sessionStorage.__nicegui_tab_closed === "false" + ? (sessionStorage.__nicegui_tab_id = createRandomUUID()) + : sessionStorage.__nicegui_tab_id; +sessionStorage.__nicegui_tab_closed = "false"; +window.onbeforeunload = function () { + sessionStorage.__nicegui_tab_closed = "true"; +}; + function createApp(elements, options) { replaceUndefinedAttributes(elements, 0); return (app = Vue.createApp({ @@ -319,12 +329,12 @@ function createApp(elements, options) { window.did_handshake = false; const messageHandlers = { connect: () => { - let tabId = sessionStorage.getItem("__nicegui_tab_id"); - if (!tabId) { - tabId = createRandomUUID(); - sessionStorage.setItem("__nicegui_tab_id", tabId); - } - window.socket.emit("handshake", { client_id: window.clientId, tab_id: tabId }, (ok) => { + const args = { + client_id: window.clientId, + tab_id: TAB_ID, + old_tab_id: OLD_TAB_ID, + }; + window.socket.emit("handshake", args, (ok) => { if (!ok) { console.log("reloading because handshake failed for clientId " + window.clientId); window.location.reload(); diff --git a/nicegui/storage.py b/nicegui/storage.py index 87a114c65..a72b8c9d9 100644 --- a/nicegui/storage.py +++ b/nicegui/storage.py @@ -191,6 +191,11 @@ def tab(self) -> observables.ObservableDict: self._tabs[client.tab_id] = observables.ObservableDict() return self._tabs[client.tab_id] + def copy_tab(self, old_tab_id: str, tab_id: str) -> None: + """Copy the tab storage to a new tab. (For internal use only.)""" + if old_tab_id in self._tabs: + self._tabs[tab_id] = observables.ObservableDict(self._tabs[old_tab_id].copy()) + async def prune_tab_storage(self) -> None: """Regularly prune tab storage that is older than the configured `max_tab_storage_age`.""" while True: