Skip to content

Commit

Permalink
Improve generation of unique tab IDs (#3876)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
falkoschindler authored Oct 20, 2024
1 parent 1f0bc6d commit 526c66b
Show file tree
Hide file tree
Showing 4 changed files with 25 additions and 6 deletions.
2 changes: 2 additions & 0 deletions nicegui/air.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 2 additions & 0 deletions nicegui/nicegui.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'}}
Expand Down
22 changes: 16 additions & 6 deletions nicegui/static/nicegui.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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();
Expand Down
5 changes: 5 additions & 0 deletions nicegui/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 526c66b

Please sign in to comment.