Fix tabNotFoundResponse to handle Fly-prefixed tab IDs

Tab IDs on Fly are '{machineId}_{uuid}'. The UUID regex check was
testing the full prefixed string, always failing, so stale tabs after
browser restart got 404 instead of 410. Now extracts the UUID portion
before validation.
This commit is contained in:
Pradeep Elankumaran
2026-05-24 09:42:00 -07:00
parent 7e623869eb
commit 3f6ecfec0f
2 changed files with 50 additions and 1 deletions
+5 -1
View File
@@ -1469,7 +1469,11 @@ function tabNotFoundResponse(res, tabId) {
// Only return 410 for tabs that look like valid UUIDs (plausibly created by this server),
// belonged to this machine, and were lost in a recent browser restart.
// Random/invalid strings like 'non-existent-tab' always get 404.
if (_lastBrowserRestartAt && (Date.now() - _lastBrowserRestartAt < 300_000) && UUID_RE.test(tabId) && fly.isLocalTab(tabId)) {
// Tab IDs may be Fly-prefixed: "{machineId}_{uuid}" — extract UUID portion for validation.
const uuidPart = tabId && tabId.includes('_') && !tabId.slice(0, tabId.indexOf('_')).includes('-')
? tabId.slice(tabId.indexOf('_') + 1)
: tabId;
if (_lastBrowserRestartAt && (Date.now() - _lastBrowserRestartAt < 300_000) && UUID_RE.test(uuidPart) && fly.isLocalTab(tabId)) {
return res.status(410).json({
error: 'Tab no longer exists (browser was restarted). Create a new tab.',
code: 'browser_restarted',
+45
View File
@@ -0,0 +1,45 @@
'use strict';
/**
* Tests for tabNotFoundResponse UUID extraction from Fly-prefixed tab IDs.
*/
// Mirrors the extraction logic in server.js tabNotFoundResponse
function extractUuidPart(tabId) {
if (tabId && tabId.includes('_') && !tabId.slice(0, tabId.indexOf('_')).includes('-')) {
return tabId.slice(tabId.indexOf('_') + 1);
}
return tabId;
}
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
describe('tabNotFoundResponse UUID extraction', () => {
test('extracts UUID from Fly-prefixed tab ID', () => {
const tabId = '68341eecdd3168_a1b2c3d4-e5f6-7890-abcd-ef1234567890';
expect(extractUuidPart(tabId)).toBe('a1b2c3d4-e5f6-7890-abcd-ef1234567890');
expect(UUID_RE.test(extractUuidPart(tabId))).toBe(true);
});
test('returns plain UUID as-is (no prefix)', () => {
const tabId = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
expect(extractUuidPart(tabId)).toBe(tabId);
expect(UUID_RE.test(extractUuidPart(tabId))).toBe(true);
});
test('does not treat UUID with dashes as prefixed', () => {
// A bare UUID has dashes in the first segment before any underscore
const tabId = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
expect(extractUuidPart(tabId)).toBe(tabId);
});
test('random garbage does not pass UUID check', () => {
expect(UUID_RE.test(extractUuidPart('non-existent-tab'))).toBe(false);
expect(UUID_RE.test(extractUuidPart('foobar'))).toBe(false);
});
test('Fly-prefixed with invalid UUID portion fails check', () => {
const tabId = '68341eecdd3168_not-a-uuid';
expect(UUID_RE.test(extractUuidPart(tabId))).toBe(false);
});
});