fix(tabs): server-authoritative tab sync across browser sessions
Changed syncTabsFromServer to replace local tabs with server tabs instead of merging (union). This prevents tabs closed in browser A from being resurrected when browser B refreshes. Also added server sync to the iframe module landing path which was localStorage-only. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4722aca065
commit
21b38e7297
|
|
@ -871,7 +871,7 @@ export function renderShell(opts: ShellOptions): string {
|
|||
if (_tabChannel) _tabChannel.close();
|
||||
});
|
||||
|
||||
// Fetch tabs from server for authenticated users (merge with localStorage)
|
||||
// Fetch tabs from server for authenticated users (server-authoritative)
|
||||
(function syncTabsFromServer() {
|
||||
try {
|
||||
const raw = localStorage.getItem('encryptid_session');
|
||||
|
|
@ -888,21 +888,17 @@ export function renderShell(opts: ShellOptions): string {
|
|||
saveTabs();
|
||||
return;
|
||||
}
|
||||
// Merge: union of moduleIds, server order wins for shared tabs
|
||||
// Skip tabs that were closed this session (prevents resurrection)
|
||||
const serverMap = new Map(data.tabs.map(t => [t.moduleId, t]));
|
||||
const localMap = new Map(layers.map(t => [t.moduleId, t]));
|
||||
const merged = data.tabs.filter(t => !_closedModuleIds.has(t.moduleId));
|
||||
for (const [mid, lt] of localMap) {
|
||||
if (!serverMap.has(mid) && !_closedModuleIds.has(mid)) merged.push(lt);
|
||||
}
|
||||
merged.forEach((l, i) => { l.order = i; });
|
||||
layers = merged;
|
||||
// Ensure current module is present
|
||||
// Server-authoritative: adopt server tabs exactly.
|
||||
// Only skip tabs the user closed in THIS session (prevents
|
||||
// close-then-immediate-resurrect before save propagates).
|
||||
var serverTabs = data.tabs.filter(t => !_closedModuleIds.has(t.moduleId));
|
||||
serverTabs.forEach((l, i) => { l.order = i; });
|
||||
layers = serverTabs;
|
||||
// Ensure the currently navigated module is present
|
||||
if (!layers.find(l => l.moduleId === currentModuleId)) {
|
||||
layers.push(makeLayer(currentModuleId, layers.length));
|
||||
}
|
||||
tabBar.setLayers(layers);
|
||||
reconcileRemoteLayers(layers);
|
||||
saveTabs();
|
||||
})
|
||||
.catch(() => {});
|
||||
|
|
@ -1455,7 +1451,44 @@ export function renderExternalAppShell(opts: ExternalAppShellOptions): string {
|
|||
tabBar.setLayers(layers);
|
||||
tabBar.setAttribute('active', 'layer-' + currentModuleId);
|
||||
if (tabBar.trackRecent) tabBar.trackRecent(currentModuleId);
|
||||
function saveTabs() { localStorage.setItem(TABS_KEY, JSON.stringify(layers)); }
|
||||
function saveTabs() {
|
||||
localStorage.setItem(TABS_KEY, JSON.stringify(layers));
|
||||
try {
|
||||
var raw = localStorage.getItem('encryptid_session');
|
||||
if (raw) {
|
||||
var session = JSON.parse(raw);
|
||||
if (session?.accessToken) {
|
||||
fetch('/api/user/tabs/' + encodeURIComponent(spaceSlug), {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + session.accessToken },
|
||||
body: JSON.stringify({ tabs: layers }),
|
||||
}).catch(function(){});
|
||||
}
|
||||
}
|
||||
} catch(e) {}
|
||||
}
|
||||
// Sync tabs from server (server-authoritative)
|
||||
(function() {
|
||||
try {
|
||||
var raw = localStorage.getItem('encryptid_session');
|
||||
if (!raw) return;
|
||||
var session = JSON.parse(raw);
|
||||
if (!session?.accessToken) return;
|
||||
fetch('/api/user/tabs/' + encodeURIComponent(spaceSlug), {
|
||||
headers: { 'Authorization': 'Bearer ' + session.accessToken },
|
||||
})
|
||||
.then(function(r) { return r.ok ? r.json() : null; })
|
||||
.then(function(data) {
|
||||
if (!data?.tabs || !Array.isArray(data.tabs) || data.tabs.length === 0) { saveTabs(); return; }
|
||||
layers = data.tabs;
|
||||
layers.forEach(function(l, i) { l.order = i; });
|
||||
if (!layers.find(function(l) { return l.moduleId === currentModuleId; })) layers.push(makeLayer(currentModuleId, layers.length));
|
||||
tabBar.setLayers(layers);
|
||||
localStorage.setItem(TABS_KEY, JSON.stringify(layers));
|
||||
})
|
||||
.catch(function(){});
|
||||
} catch(e) {}
|
||||
})();
|
||||
tabBar.addEventListener('layer-switch', (e) => { saveTabs(); window.location.href = window.__rspaceNavUrl(spaceSlug, e.detail.moduleId); });
|
||||
tabBar.addEventListener('layer-add', (e) => { const { moduleId } = e.detail; if (!layers.find(l => l.moduleId === moduleId)) layers.push(makeLayer(moduleId, layers.length)); saveTabs(); window.location.href = window.__rspaceNavUrl(spaceSlug, moduleId); });
|
||||
tabBar.addEventListener('layer-close', (e) => { const { layerId } = e.detail; tabBar.removeLayer(layerId); layers = layers.filter(l => l.id !== layerId); saveTabs(); if (layerId === 'layer-' + currentModuleId && layers.length > 0) window.location.href = window.__rspaceNavUrl(spaceSlug, layers[0].moduleId); });
|
||||
|
|
|
|||
Loading…
Reference in New Issue