浏览代码

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.
Falko Schindler 7 月之前
父节点
当前提交
526c66b79a
共有 4 个文件被更改,包括 25 次插入6 次删除
  1. 2 0
      nicegui/air.py
  2. 2 0
      nicegui/nicegui.py
  3. 16 6
      nicegui/static/nicegui.js
  4. 5 0
      nicegui/storage.py

+ 2 - 0
nicegui/air.py

@@ -130,6 +130,8 @@ class Air:
                 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()

+ 2 - 0
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'}}

+ 16 - 6
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();

+ 5 - 0
nicegui/storage.py

@@ -191,6 +191,11 @@ class Storage:
             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: