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();
|
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() {
|
(function syncTabsFromServer() {
|
||||||
try {
|
try {
|
||||||
const raw = localStorage.getItem('encryptid_session');
|
const raw = localStorage.getItem('encryptid_session');
|
||||||
|
|
@ -888,21 +888,17 @@ export function renderShell(opts: ShellOptions): string {
|
||||||
saveTabs();
|
saveTabs();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Merge: union of moduleIds, server order wins for shared tabs
|
// Server-authoritative: adopt server tabs exactly.
|
||||||
// Skip tabs that were closed this session (prevents resurrection)
|
// Only skip tabs the user closed in THIS session (prevents
|
||||||
const serverMap = new Map(data.tabs.map(t => [t.moduleId, t]));
|
// close-then-immediate-resurrect before save propagates).
|
||||||
const localMap = new Map(layers.map(t => [t.moduleId, t]));
|
var serverTabs = data.tabs.filter(t => !_closedModuleIds.has(t.moduleId));
|
||||||
const merged = data.tabs.filter(t => !_closedModuleIds.has(t.moduleId));
|
serverTabs.forEach((l, i) => { l.order = i; });
|
||||||
for (const [mid, lt] of localMap) {
|
layers = serverTabs;
|
||||||
if (!serverMap.has(mid) && !_closedModuleIds.has(mid)) merged.push(lt);
|
// Ensure the currently navigated module is present
|
||||||
}
|
|
||||||
merged.forEach((l, i) => { l.order = i; });
|
|
||||||
layers = merged;
|
|
||||||
// Ensure current module is present
|
|
||||||
if (!layers.find(l => l.moduleId === currentModuleId)) {
|
if (!layers.find(l => l.moduleId === currentModuleId)) {
|
||||||
layers.push(makeLayer(currentModuleId, layers.length));
|
layers.push(makeLayer(currentModuleId, layers.length));
|
||||||
}
|
}
|
||||||
tabBar.setLayers(layers);
|
reconcileRemoteLayers(layers);
|
||||||
saveTabs();
|
saveTabs();
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
|
|
@ -1455,7 +1451,44 @@ export function renderExternalAppShell(opts: ExternalAppShellOptions): string {
|
||||||
tabBar.setLayers(layers);
|
tabBar.setLayers(layers);
|
||||||
tabBar.setAttribute('active', 'layer-' + currentModuleId);
|
tabBar.setAttribute('active', 'layer-' + currentModuleId);
|
||||||
if (tabBar.trackRecent) tabBar.trackRecent(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-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-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); });
|
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